diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 65a3646f..7c86b7b0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,7 +24,7 @@ jobs: - name: Build run: | dotnet build --no-restore '-p:SkipWebApp=true;SkipTranscoder=true' -p:CopyLocalLockFileAssemblies=true - cp ./Kyoo.Common/bin/Debug/net5.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll ./Kyoo.Tests/bin/Debug/net5.0/ + cp ./Kyoo.Common/bin/Debug/net5.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll ./tests/Kyoo.Tests/bin/Debug/net5.0/ - name: Test run: dotnet test --no-build '-p:CollectCoverage=true;CoverletOutputFormat=opencover' env: @@ -33,7 +33,7 @@ jobs: POSTGRES_PASSWORD: postgres - name: Sanitize coverage output if: ${{ always() }} - run: sed -i "s'$(pwd)'.'" Kyoo.Tests/coverage.opencover.xml + run: sed -i "s'$(pwd)'.'" tests/Kyoo.Tests/coverage.opencover.xml - name: Upload coverage report if: ${{ always() }} uses: actions/upload-artifact@v2 diff --git a/Kyoo.Common/Controllers/IFileSystem.cs b/Kyoo.Common/Controllers/IFileSystem.cs index 2fb1c6d7..92e951b7 100644 --- a/Kyoo.Common/Controllers/IFileSystem.cs +++ b/Kyoo.Common/Controllers/IFileSystem.cs @@ -2,11 +2,25 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using JetBrains.Annotations; -using Kyoo.Models; using Microsoft.AspNetCore.Mvc; namespace Kyoo.Controllers { + /// + /// A class wrapping a value that will be set after the completion of the task it is related to. + /// + /// + /// This class replace the use of an out parameter on a task since tasks and out can't be combined. + /// + /// The type of the value + public class AsyncRef + { + /// + /// The value that will be set before the completion of the task. + /// + public T Value { get; set; } + } + /// /// A service to abstract the file system to allow custom file systems (like distant file systems or external providers) /// @@ -42,6 +56,16 @@ namespace Kyoo.Controllers /// If the file could not be found. /// A reader to read the file. public Task GetReader([NotNull] string path); + + /// + /// Read a file present at . The reader can be used in an arbitrary context. + /// To return files from an http endpoint, use . + /// + /// The path of the file + /// The mime type of the opened file. + /// If the file could not be found. + /// A reader to read the file. + public Task GetReader([NotNull] string path, AsyncRef mime); /// /// Create a new file at . @@ -81,12 +105,13 @@ namespace Kyoo.Controllers public Task Exists([NotNull] string path); /// - /// Get the extra directory of a show. + /// Get the extra directory of a resource . /// This method is in this system to allow a filesystem to use a different metadata policy for one. /// It can be useful if the filesystem is readonly. /// - /// The show to proceed - /// The extra directory of the show - public string GetExtraDirectory([NotNull] Show show); + /// The resource to proceed + /// The type of the resource. + /// The extra directory of the resource. + public Task GetExtraDirectory([NotNull] T resource); } } \ No newline at end of file diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 938d3029..8c3442e3 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -225,42 +225,51 @@ namespace Kyoo.Controllers /// /// The source object. /// A getter function for the member to load + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// /// The type of the source object /// The related resource's type /// The param - /// - /// - /// - Task Load([NotNull] T obj, Expression> member) + /// + /// + /// + Task Load([NotNull] T obj, Expression> member, bool force = false) where T : class, IResource - where T2 : class, IResource, new(); + where T2 : class, IResource; /// /// Load a collection of related resource /// /// The source object. /// A getter function for the member to load + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// /// The type of the source object /// The related resource's type /// The param - /// - /// - /// - Task Load([NotNull] T obj, Expression>> member) + /// + /// + /// + Task Load([NotNull] T obj, Expression>> member, bool force = false) where T : class, IResource - where T2 : class, new(); + where T2 : class; /// /// Load a related resource by it's name /// /// The source object. /// The name of the resource to load (case sensitive) + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// /// The type of the source object /// The param - /// - /// - /// - Task Load([NotNull] T obj, string memberName) + /// + /// + /// + Task Load([NotNull] T obj, string memberName, bool force = false) where T : class, IResource; /// @@ -268,10 +277,13 @@ namespace Kyoo.Controllers /// /// The source object. /// The name of the resource to load (case sensitive) - /// - /// - /// - Task Load([NotNull] IResource obj, string memberName); + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// + /// + /// + /// + Task Load([NotNull] IResource obj, string memberName, bool force = false); /// /// Get items (A wrapper arround shows or collections) from a library. diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index a83d17fa..c5fe7a7c 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -590,10 +590,10 @@ namespace Kyoo.Controllers /// Pagination information (where to start and how many to get) /// The type of metadata to retrieve /// A filtered list of external ids. - Task>> GetMetadataID(Expression, bool>> where = null, - Sort> sort = default, + Task> GetMetadataID(Expression> where = null, + Sort sort = default, Pagination limit = default) - where T : class, IResource; + where T : class, IMetadata; /// /// Get a list of external ids that match all filters @@ -602,11 +602,11 @@ namespace Kyoo.Controllers /// A sort by expression /// Pagination information (where to start and how many to get) /// A filtered list of external ids. - Task>> GetMetadataID([Optional] Expression, bool>> where, - Expression, object>> sort, + Task> GetMetadataID([Optional] Expression> where, + Expression> sort, Pagination limit = default - ) where T : class, IResource - => GetMetadataID(where, new Sort>(sort), limit); + ) where T : class, IMetadata + => GetMetadataID(where, new Sort(sort), limit); } /// diff --git a/Kyoo.Common/Controllers/IThumbnailsManager.cs b/Kyoo.Common/Controllers/IThumbnailsManager.cs index 465d4c62..61fe5345 100644 --- a/Kyoo.Common/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Common/Controllers/IThumbnailsManager.cs @@ -1,5 +1,4 @@ -using System; -using Kyoo.Models; +using Kyoo.Models; using System.Threading.Tasks; using JetBrains.Annotations; @@ -23,37 +22,17 @@ namespace Kyoo.Controllers /// The type of the item /// true if an image has been downloaded, false otherwise. Task DownloadImages([NotNull] T item, bool alwaysDownload = false) - where T : IResource; - + where T : IThumbnails; + /// - /// Retrieve the local path of the poster of the given item. + /// Retrieve the local path of an image of the given item. /// /// The item to retrieve the poster from. + /// The ID of the image. See for values. /// The type of the item - /// If the type does not have a poster - /// The path of the poster for the given resource (it might or might not exists). - Task GetPoster([NotNull] T item) - where T : IResource; - - /// - /// Retrieve the local path of the logo of the given item. - /// - /// The item to retrieve the logo from. - /// The type of the item - /// If the type does not have a logo - /// The path of the logo for the given resource (it might or might not exists). - Task GetLogo([NotNull] T item) - where T : IResource; - - /// - /// Retrieve the local path of the thumbnail of the given item. - /// - /// The item to retrieve the thumbnail from. - /// The type of the item - /// If the type does not have a thumbnail - /// The path of the thumbnail for the given resource (it might or might not exists). - Task GetThumbnail([NotNull] T item) - where T : IResource; + /// The path of the image for the given resource or null if it does not exists. + Task GetImagePath([NotNull] T item, int imageID) + where T : IThumbnails; } } diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 12ea0b2a..08475c32 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -162,34 +162,6 @@ namespace Kyoo.Controllers return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber); } - /// - public Task Load(T obj, Expression> member) - where T : class, IResource - where T2 : class, IResource, new() - { - if (member == null) - throw new ArgumentNullException(nameof(member)); - return Load(obj, Utility.GetPropertyName(member)); - } - - /// - public Task Load(T obj, Expression>> member) - where T : class, IResource - where T2 : class, new() - { - if (member == null) - throw new ArgumentNullException(nameof(member)); - return Load(obj, Utility.GetPropertyName(member)); - } - - /// - public async Task Load(T obj, string memberName) - where T : class, IResource - { - await Load(obj as IResource, memberName); - return obj; - } - /// /// Set relations between to objects. /// @@ -211,11 +183,46 @@ namespace Kyoo.Controllers } /// - public Task Load(IResource obj, string memberName) + public Task Load(T obj, Expression> member, bool force = false) + where T : class, IResource + where T2 : class, IResource + { + if (member == null) + throw new ArgumentNullException(nameof(member)); + return Load(obj, Utility.GetPropertyName(member), force); + } + + /// + public Task Load(T obj, Expression>> member, bool force = false) + where T : class, IResource + where T2 : class + { + if (member == null) + throw new ArgumentNullException(nameof(member)); + return Load(obj, Utility.GetPropertyName(member), force); + } + + /// + public async Task Load(T obj, string memberName, bool force = false) + where T : class, IResource + { + await Load(obj as IResource, memberName, force); + return obj; + } + + /// + public Task Load(IResource obj, string memberName, bool force = false) { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + + object existingValue = obj.GetType() + .GetProperties() + .FirstOrDefault(x => string.Equals(x.Name, memberName, StringComparison.InvariantCultureIgnoreCase)) + ?.GetValue(obj); + if (existingValue != null && !force) + return Task.CompletedTask; + return (obj, member: memberName) switch { (Library l, nameof(Library.Providers)) => ProviderRepository @@ -231,7 +238,12 @@ namespace Kyoo.Controllers .Then(x => l.Collections = x), - (Collection c, nameof(Library.Shows)) => ShowRepository + (Collection c, nameof(Collection.ExternalIDs)) => SetRelation(c, + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), + (x, y) => x.ExternalIDs = y, + (x, y) => { x.ResourceID = y.ID; }), + + (Collection c, nameof(Collection.Shows)) => ShowRepository .GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) .Then(x => c.Shows = x), @@ -241,9 +253,9 @@ namespace Kyoo.Controllers (Show s, nameof(Show.ExternalIDs)) => SetRelation(s, - ProviderRepository.GetMetadataID(x => x.FirstID == obj.ID), + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, - (x, y) => { x.First = y; x.FirstID = y.ID; }), + (x, y) => { x.ResourceID = y.ID; }), (Show s, nameof(Show.Genres)) => GenreRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) @@ -281,9 +293,9 @@ namespace Kyoo.Controllers (Season s, nameof(Season.ExternalIDs)) => SetRelation(s, - ProviderRepository.GetMetadataID(x => x.FirstID == obj.ID), + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, - (x, y) => { x.First = y; x.FirstID = y.ID; }), + (x, y) => { x.ResourceID = y.ID; }), (Season s, nameof(Season.Episodes)) => SetRelation(s, EpisodeRepository.GetAll(x => x.Season.ID == obj.ID), @@ -300,9 +312,9 @@ namespace Kyoo.Controllers (Episode e, nameof(Episode.ExternalIDs)) => SetRelation(e, - ProviderRepository.GetMetadataID(x => x.FirstID == obj.ID), + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, - (x, y) => { x.First = y; x.FirstID = y.ID; }), + (x, y) => { x.ResourceID = y.ID; }), (Episode e, nameof(Episode.Tracks)) => SetRelation(e, TrackRepository.GetAll(x => x.Episode.ID == obj.ID), @@ -344,11 +356,16 @@ namespace Kyoo.Controllers .GetAll(x => x.Studio.ID == obj.ID) .Then(x => s.Shows = x), + (Studio s, nameof(Studio.ExternalIDs)) => SetRelation(s, + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), + (x, y) => x.ExternalIDs = y, + (x, y) => { x.ResourceID = y.ID; }), + (People p, nameof(People.ExternalIDs)) => SetRelation(p, - ProviderRepository.GetMetadataID(x => x.FirstID == obj.ID), + ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, - (x, y) => { x.First = y; x.FirstID = y.ID; }), + (x, y) => { x.ResourceID = y.ID; }), (People p, nameof(People.Roles)) => PeopleRepository .GetFromPeople(obj.ID) diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index 844be997..ec328eab 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -16,13 +16,11 @@ true snupkg default - - ENABLE_INTERNAL_LINKS - + diff --git a/Kyoo.Common/Models/Attributes/LinkAttribute.cs b/Kyoo.Common/Models/Attributes/LinkAttribute.cs deleted file mode 100644 index d98ad90a..00000000 --- a/Kyoo.Common/Models/Attributes/LinkAttribute.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using JetBrains.Annotations; -using Kyoo.Models.Attributes; - -namespace Kyoo.Common.Models.Attributes -{ - /// - /// An attribute to mark Link properties on resource. - /// - [AttributeUsage(AttributeTargets.Property)] - [MeansImplicitUse] - public class LinkAttribute : SerializeIgnoreAttribute { } -} \ No newline at end of file diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs index 6bc61c2e..18680b9e 100644 --- a/Kyoo.Common/Models/LibraryItem.cs +++ b/Kyoo.Common/Models/LibraryItem.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq.Expressions; using Kyoo.Models.Attributes; @@ -18,7 +19,7 @@ namespace Kyoo.Models /// A type union between and . /// This is used to list content put inside a library. /// - public class LibraryItem : IResource + public class LibraryItem : IResource, IThumbnails { /// public int ID { get; set; } @@ -52,13 +53,17 @@ namespace Kyoo.Models /// It can also be null if this is unknown. /// public DateTime? EndAir { get; set; } - + + /// + public Dictionary Images { get; set; } + /// /// The path of this item's poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); /// /// The type of this item (ether a collection, a show or a movie). @@ -84,7 +89,7 @@ namespace Kyoo.Models Status = show.Status; StartAir = show.StartAir; EndAir = show.EndAir; - Poster = show.Poster; + Images = show.Images; Type = show.IsMovie ? ItemType.Movie : ItemType.Show; } @@ -101,7 +106,7 @@ namespace Kyoo.Models Status = Models.Status.Unknown; StartAir = null; EndAir = null; - Poster = collection.Poster; + Images = collection.Images; Type = ItemType.Collection; } @@ -117,7 +122,7 @@ namespace Kyoo.Models Status = x.Status, StartAir = x.StartAir, EndAir = x.EndAir, - Poster= x.Poster, + Images = x.Images, Type = x.IsMovie ? ItemType.Movie : ItemType.Show }; @@ -133,7 +138,7 @@ namespace Kyoo.Models Status = Models.Status.Unknown, StartAir = null, EndAir = null, - Poster = x.Poster, + Images = x.Images, Type = ItemType.Collection }; } diff --git a/Kyoo.Common/Models/Link.cs b/Kyoo.Common/Models/Link.cs deleted file mode 100644 index 09c519da..00000000 --- a/Kyoo.Common/Models/Link.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.Linq.Expressions; -using Kyoo.Models.Attributes; - -namespace Kyoo.Models -{ - /// - /// A class representing a link between two resources. - /// - /// - /// Links should only be used on the data layer and not on other application code. - /// - public class Link - { - /// - /// The ID of the first item of the link. - /// The first item of the link should be the one to own the link. - /// - public int FirstID { get; set; } - - /// - /// The ID of the second item of this link - /// The second item of the link should be the owned resource. - /// - public int SecondID { get; set; } - - /// - /// Create a new typeless . - /// - public Link() {} - - /// - /// Create a new typeless with two IDs. - /// - /// The ID of the first resource - /// The ID of the second resource - public Link(int firstID, int secondID) - { - FirstID = firstID; - SecondID = secondID; - } - - /// - /// Create a new typeless between two resources. - /// - /// The first resource - /// The second resource - public Link(IResource first, IResource second) - { - FirstID = first.ID; - SecondID = second.ID; - } - - /// - /// Create a new typed link between two resources. - /// This method can be used instead of the constructor to make use of generic parameters deduction. - /// - /// The first resource - /// The second resource - /// The type of the first resource - /// The type of the second resource - /// A newly created typed link with both resources - public static Link Create(T first, T2 second) - where T : class, IResource - where T2 : class, IResource - { - return new(first, second); - } - - /// - /// Create a new typed link between two resources without storing references to resources. - /// This is the same as but this method does not set - /// and fields. Only IDs are stored and not references. - /// - /// The first resource - /// The second resource - /// The type of the first resource - /// The type of the second resource - /// A newly created typed link with both resources - public static Link UCreate(T first, T2 second) - where T : class, IResource - where T2 : class, IResource - { - return new(first, second, true); - } - - /// - /// The expression to retrieve the unique ID of a Link. This is an aggregate of the two resources IDs. - /// - public static Expression> PrimaryKey - { - get - { - return x => new {First = x.FirstID, Second = x.SecondID}; - } - } - } - - /// - /// A strongly typed link between two resources. - /// - /// The type of the first resource - /// The type of the second resource - public class Link : Link - where T1 : class, IResource - where T2 : class, IResource - { - /// - /// A reference of the first resource. - /// - [SerializeIgnore] public T1 First { get; set; } - - /// - /// A reference to the second resource. - /// - [SerializeIgnore] public T2 Second { get; set; } - - - /// - /// Create a new, empty, typed . - /// - public Link() {} - - - /// - /// Create a new typed link with two resources. - /// - /// The first resource - /// The second resource - /// - /// True if no reference to resources should be kept, false otherwise. - /// The default is false (references are kept). - /// - public Link(T1 first, T2 second, bool privateItems = false) - : base(first, second) - { - if (privateItems) - return; - First = first; - Second = second; - } - - /// - /// Create a new typed link with IDs only. - /// - /// The ID of the first resource - /// The ID of the second resource - public Link(int firstID, int secondID) - : base(firstID, secondID) - { } - - /// - /// The expression to retrieve the unique ID of a typed Link. This is an aggregate of the two resources IDs. - /// - public new static Expression, object>> PrimaryKey - { - get - { - return x => new {First = x.FirstID, Second = x.SecondID}; - } - } - } -} \ No newline at end of file diff --git a/Kyoo.Common/Models/MetadataID.cs b/Kyoo.Common/Models/MetadataID.cs index de6ac817..cda4ce11 100644 --- a/Kyoo.Common/Models/MetadataID.cs +++ b/Kyoo.Common/Models/MetadataID.cs @@ -1,40 +1,45 @@ using System; using System.Linq.Expressions; +using Kyoo.Models.Attributes; namespace Kyoo.Models { /// /// ID and link of an item on an external provider. /// - /// - public class MetadataID : Link - where T : class, IResource + public class MetadataID { + /// + /// The ID of the resource which possess the metadata. + /// + [SerializeIgnore] public int ResourceID { get; set; } + + /// + /// The ID of the provider. + /// + [SerializeIgnore] public int ProviderID { get; set; } + + /// + /// The provider that can do something with this ID. + /// + public Provider Provider { get; set; } + /// /// The ID of the resource on the external provider. /// public string DataID { get; set; } - + /// /// The URL of the resource on the external provider. /// public string Link { get; set; } - /// - /// A shortcut to access the provider of this metadata. - /// Unlike the property, this is serializable. - /// - public Provider Provider => Second; - /// /// The expression to retrieve the unique ID of a MetadataID. This is an aggregate of the two resources IDs. /// - public new static Expression, object>> PrimaryKey + public static Expression> PrimaryKey { - get - { - return x => new {First = x.FirstID, Second = x.SecondID}; - } + get { return x => new { First = x.ResourceID, Second = x.ProviderID }; } } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/PeopleRole.cs b/Kyoo.Common/Models/PeopleRole.cs index 062bb3e8..daf9c7cd 100644 --- a/Kyoo.Common/Models/PeopleRole.cs +++ b/Kyoo.Common/Models/PeopleRole.cs @@ -17,8 +17,8 @@ namespace Kyoo.Models public string Slug => ForPeople ? Show.Slug : People.Slug; /// - /// Should this role be used as a Show substitute (the value is false) or - /// as a People substitute (the value is true). + /// Should this role be used as a Show substitute (the value is true) or + /// as a People substitute (the value is false). /// public bool ForPeople { get; set; } diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 8162ff16..20cc481b 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; +using System; +using System.Collections.Generic; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -8,7 +8,7 @@ namespace Kyoo.Models /// A class representing collections of . /// A collection can also be stored in a . /// - public class Collection : IResource + public class Collection : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -20,14 +20,19 @@ namespace Kyoo.Models /// The name of this collection. /// public string Name { get; set; } + + /// + public Dictionary Images { get; set; } /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/collection/{Slug}/poster")] public string Poster { get; set; } - + [SerializeAs("{HOST}/api/collection/{Slug}/poster")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); + /// /// The description of this collection. /// @@ -42,18 +47,8 @@ namespace Kyoo.Models /// The list of libraries that contains this collection. /// [LoadableRelation] public ICollection Libraries { get; set; } - -#if ENABLE_INTERNAL_LINKS - /// - /// The internal link between this collection and shows in the list. - /// - [Link] public ICollection> ShowLinks { get; set; } - - /// - /// The internal link between this collection and libraries in the list. - /// - [Link] public ICollection> LibraryLinks { get; set; } -#endif + /// + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } } } diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 7f76a8a4..dd1f62e1 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models /// /// A class to represent a single show's episode. /// - public class Episode : IResource + public class Episode : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -74,9 +74,13 @@ namespace Kyoo.Models /// [SerializeIgnore] public int? SeasonID { get; set; } /// - /// The season that contains this episode. This must be explicitly loaded via a call to . - /// This can be null if the season is unknown and the episode is only identified by it's . + /// The season that contains this episode. + /// This must be explicitly loaded via a call to . /// + /// + /// This can be null if the season is unknown and the episode is only identified + /// by it's . + /// [LoadableRelation(nameof(SeasonID))] public Season Season { get; set; } /// @@ -85,7 +89,7 @@ namespace Kyoo.Models public int? SeasonNumber { get; set; } /// - /// The number of this episode is it's season. + /// The number of this episode in it's season. /// public int? EpisodeNumber { get; set; } @@ -98,13 +102,18 @@ namespace Kyoo.Models /// The path of the video file for this episode. Any format supported by a is allowed. /// [SerializeIgnore] public string Path { get; set; } + + /// + public Dictionary Images { get; set; } /// /// The path of this episode's thumbnail. /// By default, the http path for the thumbnail is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] public string Thumb { get; set; } + [SerializeAs("{HOST}/api/episodes/{Slug}/thumbnail")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] + public string Thumb => Images?.GetValueOrDefault(Models.Images.Thumbnail); /// /// The title of this episode. @@ -121,10 +130,8 @@ namespace Kyoo.Models /// public DateTime? ReleaseDate { get; set; } - /// - /// The link to metadata providers that this episode has. See for more information. - /// - [EditableRelation] [LoadableRelation] public ICollection> ExternalIDs { get; set; } + /// + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The list of tracks this episode has. This lists video, audio and subtitles available. diff --git a/Kyoo.Common/Models/Resources/Genre.cs b/Kyoo.Common/Models/Resources/Genre.cs index c7aaa76f..e5fcbed8 100644 --- a/Kyoo.Common/Models/Resources/Genre.cs +++ b/Kyoo.Common/Models/Resources/Genre.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -24,14 +23,7 @@ namespace Kyoo.Models /// The list of shows that have this genre. /// [LoadableRelation] public ICollection Shows { get; set; } - -#if ENABLE_INTERNAL_LINKS - /// - /// The internal link between this genre and shows in the list. - /// - [Link] public ICollection> ShowLinks { get; set; } -#endif - + /// /// Create a new, empty . /// diff --git a/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs new file mode 100644 index 00000000..ef1e6bbc --- /dev/null +++ b/Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using Kyoo.Models.Attributes; + +namespace Kyoo.Models +{ + /// + /// An interface applied to resources containing external metadata. + /// + public interface IMetadata + { + /// + /// The link to metadata providers that this show has. See for more information. + /// + [EditableRelation] [LoadableRelation] + public ICollection ExternalIDs { get; set; } + } + + /// + /// A static class containing extensions method for every class. + /// This allow one to use metadata more easily. + /// + public static class MetadataExtension + { + /// + /// Retrieve the internal provider's ID of an item using it's provider slug. + /// + /// + /// This method will never return anything if the are not loaded. + /// + /// An instance of to retrieve the ID from. + /// The slug of the provider + /// The field of the asked provider. + [CanBeNull] + public static string GetID(this IMetadata self, string provider) + { + return self.ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID; + } + + /// + /// Retrieve the internal provider's ID of an item using it's provider slug. + /// If the ID could be found, it is converted to the type and true is returned. + /// + /// + /// This method will never succeed if the are not loaded. + /// + /// An instance of to retrieve the ID from. + /// The slug of the provider + /// + /// The field of the asked provider parsed + /// and converted to the type. + /// It is only relevant if this method returns true. + /// + /// The type to convert the to. + /// true if this method succeeded, false otherwise. + public static bool TryGetID(this IMetadata self, string provider, out T id) + { + string dataID = self.ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID; + if (dataID == null) + { + id = default; + return false; + } + + try + { + id = (T)Convert.ChangeType(dataID, typeof(T)); + } + catch + { + id = default; + return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/IResource.cs b/Kyoo.Common/Models/Resources/Interfaces/IResource.cs similarity index 100% rename from Kyoo.Common/Models/Resources/IResource.cs rename to Kyoo.Common/Models/Resources/Interfaces/IResource.cs diff --git a/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs b/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs new file mode 100644 index 00000000..ba4999de --- /dev/null +++ b/Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using Kyoo.Controllers; + +namespace Kyoo.Models +{ + /// + /// An interface representing items that contains images (like posters, thumbnails, logo, banners...) + /// + public interface IThumbnails + { + /// + /// The list of images mapped to a certain index. + /// The string value should be a path supported by the . + /// + /// + /// An arbitrary index should not be used, instead use indexes from + /// + public Dictionary Images { get; set; } + + // TODO remove Posters properties add them via the json serializer for every IThumbnails + } + + /// + /// A class containing constant values for images. To be used as index of a . + /// + public static class Images + { + /// + /// A poster is a 9/16 format image with the cover of the resource. + /// + public const int Poster = 0; + + /// + /// A thumbnail is a 16/9 format image, it could ether be used as a background or as a preview but it usually + /// is not an official image. + /// + public const int Thumbnail = 1; + + /// + /// A logo is a small image representing the resource. + /// + public const int Logo = 2; + + /// + /// A video of a few minutes that tease the content. + /// + public const int Trailer = 3; + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index a72d6a37..6580f988 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -39,22 +38,5 @@ namespace Kyoo.Models /// The list of collections in this library. /// [LoadableRelation] public ICollection Collections { get; set; } - -#if ENABLE_INTERNAL_LINKS - /// - /// The internal link between this library and provider in the list. - /// - [Link] public ICollection> ProviderLinks { get; set; } - - /// - /// The internal link between this library and shows in the list. - /// - [Link] public ICollection> ShowLinks { get; set; } - - /// - /// The internal link between this library and collection in the list. - /// - [Link] public ICollection> CollectionLinks { get; set; } -#endif } } diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index 7ae04613..7bd59620 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -6,7 +7,7 @@ namespace Kyoo.Models /// /// An actor, voice actor, writer, animator, somebody who worked on a . /// - public class People : IResource + public class People : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -19,17 +20,20 @@ namespace Kyoo.Models /// public string Name { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/people/{Slug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/people/{Slug}/poster")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); - /// - /// The link to metadata providers that this person has. See for more information. - /// - [EditableRelation] [LoadableRelation] public ICollection> ExternalIDs { get; set; } + /// + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The list of roles this person has played in. See for more information. diff --git a/Kyoo.Common/Models/Resources/Provider.cs b/Kyoo.Common/Models/Resources/Provider.cs index e13a9be4..31b87f3f 100644 --- a/Kyoo.Common/Models/Resources/Provider.cs +++ b/Kyoo.Common/Models/Resources/Provider.cs @@ -1,5 +1,5 @@ +using System; using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; using Kyoo.Controllers; using Kyoo.Models.Attributes; @@ -9,7 +9,7 @@ namespace Kyoo.Models /// This class contains metadata about . /// You can have providers even if you don't have the corresponding . /// - public class Provider : IResource + public class Provider : IResource, IThumbnails { /// public int ID { get; set; } @@ -22,30 +22,23 @@ namespace Kyoo.Models /// public string Name { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this provider's logo. /// By default, the http path for this logo is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/providers/{Slug}/logo")] public string Logo { get; set; } - - /// - /// The extension of the logo. This is used for http responses. - /// - [SerializeIgnore] public string LogoExtension { get; set; } - + [SerializeAs("{HOST}/api/providers/{Slug}/logo")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] + public string Logo => Images?.GetValueOrDefault(Models.Images.Logo); + /// /// The list of libraries that uses this provider. /// [LoadableRelation] public ICollection Libraries { get; set; } - -#if ENABLE_INTERNAL_LINKS - /// - /// The internal link between this provider and libraries in the list. - /// - [Link] public ICollection> LibraryLinks { get; set; } -#endif - + /// /// Create a new, default, /// @@ -61,7 +54,10 @@ namespace Kyoo.Models { Slug = Utility.ToSlug(name); Name = name; - Logo = logo; + Images = new Dictionary + { + [Models.Images.Logo] = logo + }; } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 9b292020..2c5d59eb 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models /// /// A season of a . /// - public class Season : IResource + public class Season : IResource, IMetadata, IThumbnails { /// public int ID { get; set; } @@ -45,7 +45,8 @@ namespace Kyoo.Models /// [SerializeIgnore] public int ShowID { get; set; } /// - /// The show that contains this season. This must be explicitly loaded via a call to . + /// The show that contains this season. + /// This must be explicitly loaded via a call to . /// [LoadableRelation(nameof(ShowID))] public Show Show { get; set; } @@ -74,17 +75,20 @@ namespace Kyoo.Models /// public DateTime? EndDate { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); - /// - /// The link to metadata providers that this episode has. See for more information. - /// - [EditableRelation] [LoadableRelation] public ICollection> ExternalIDs { get; set; } + /// + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } /// /// The list of episodes that this season contains. diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 57c9fcef..03c86e5e 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Kyoo.Common.Models.Attributes; using Kyoo.Controllers; using Kyoo.Models.Attributes; @@ -11,7 +8,7 @@ namespace Kyoo.Models /// /// A series or a movie. /// - public class Show : IResource, IOnMerge + public class Show : IResource, IMetadata, IOnMerge, IThumbnails { /// public int ID { get; set; } @@ -44,12 +41,13 @@ namespace Kyoo.Models /// Is this show airing, not aired yet or finished? /// public Status Status { get; set; } - + /// /// An URL to a trailer. This could be any path supported by the . /// /// TODO for now, this is set to a youtube url. It should be cached and converted to a local file. - public string TrailerUrl { get; set; } + [Obsolete("Use Images instead of this, this is only kept for the API response.")] + public string TrailerUrl => Images?.GetValueOrDefault(Models.Images.Trailer); /// /// The date this show started airing. It can be null if this is unknown. @@ -63,43 +61,51 @@ namespace Kyoo.Models /// public DateTime? EndAir { get; set; } + /// + public Dictionary Images { get; set; } + /// /// The path of this show's poster. /// By default, the http path for this poster is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; } - + [SerializeAs("{HOST}/api/shows/{Slug}/poster")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] + public string Poster => Images?.GetValueOrDefault(Models.Images.Poster); + /// /// The path of this show's logo. /// By default, the http path for this logo is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; } - + [SerializeAs("{HOST}/api/shows/{Slug}/logo")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] + public string Logo => Images?.GetValueOrDefault(Models.Images.Logo); + /// /// The path of this show's backdrop. /// By default, the http path for this backdrop is returned from the public API. /// This can be disabled using the internal query flag. /// - [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; } + [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] + [Obsolete("Use Images instead of this, this is only kept for the API response.")] + public string Backdrop => Images?.GetValueOrDefault(Models.Images.Thumbnail); /// /// True if this show represent a movie, false otherwise. /// public bool IsMovie { get; set; } - /// - /// The link to metadata providers that this show has. See for more information. - /// - [EditableRelation] [LoadableRelation] public ICollection> ExternalIDs { get; set; } - + /// + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } + /// /// The ID of the Studio that made this show. /// [SerializeIgnore] public int? StudioID { get; set; } /// - /// The Studio that made this show. This must be explicitly loaded via a call to . + /// The Studio that made this show. + /// This must be explicitly loaded via a call to . /// [LoadableRelation(nameof(StudioID))] [EditableRelation] public Studio Studio { get; set; } @@ -135,41 +141,9 @@ namespace Kyoo.Models /// [LoadableRelation] public ICollection Collections { get; set; } -#if ENABLE_INTERNAL_LINKS - /// - /// The internal link between this show and libraries in the list. - /// - [Link] public ICollection> LibraryLinks { get; set; } - - /// - /// The internal link between this show and collections in the list. - /// - [Link] public ICollection> CollectionLinks { get; set; } - - /// - /// The internal link between this show and genres in the list. - /// - [Link] public ICollection> GenreLinks { get; set; } -#endif - - /// - /// Retrieve the internal provider's ID of a show using it's provider slug. - /// - /// This method will never return anything if the are not loaded. - /// The slug of the provider - /// The field of the asked provider. - [CanBeNull] - public string GetID(string provider) - { - return ExternalIDs?.FirstOrDefault(x => x.Second.Slug == provider)?.DataID; - } - /// public void OnMerge(object merged) { - if (ExternalIDs != null) - foreach (MetadataID id in ExternalIDs) - id.First = this; if (People != null) foreach (PeopleRole link in People) link.Show = this; diff --git a/Kyoo.Common/Models/Resources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs index ebc3c4c1..03ec04df 100644 --- a/Kyoo.Common/Models/Resources/Studio.cs +++ b/Kyoo.Common/Models/Resources/Studio.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models /// /// A studio that make shows. /// - public class Studio : IResource + public class Studio : IResource, IMetadata { /// public int ID { get; set; } @@ -24,6 +24,9 @@ namespace Kyoo.Models /// [LoadableRelation] public ICollection Shows { get; set; } + /// + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } + /// /// Create a new, empty, . /// diff --git a/Kyoo.Common/Models/Resources/User.cs b/Kyoo.Common/Models/Resources/User.cs index 05f56534..3400df62 100644 --- a/Kyoo.Common/Models/Resources/User.cs +++ b/Kyoo.Common/Models/Resources/User.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; -using Kyoo.Common.Models.Attributes; namespace Kyoo.Models { /// /// A single user of the app. /// - public class User : IResource + public class User : IResource, IThumbnails { /// public int ID { get; set; } @@ -38,7 +37,10 @@ namespace Kyoo.Models /// Arbitrary extra data that can be used by specific authentication implementations. /// public Dictionary ExtraData { get; set; } - + + /// + public Dictionary Images { get; set; } + /// /// The list of shows the user has finished. /// @@ -48,20 +50,28 @@ namespace Kyoo.Models /// The list of episodes the user is watching (stopped in progress or the next episode of the show) /// public ICollection CurrentlyWatching { get; set; } - -#if ENABLE_INTERNAL_LINKS - /// - /// Links between Users and Shows. - /// - [Link] public ICollection> ShowLinks { get; set; } -#endif } /// /// Metadata of episode currently watching by an user /// - public class WatchedEpisode : Link + public class WatchedEpisode { + /// + /// The ID of the user that started watching this episode. + /// + public int UserID { get; set; } + + /// + /// The ID of the episode started. + /// + public int EpisodeID { get; set; } + + /// + /// The started. + /// + public Episode Episode { get; set; } + /// /// Where the player has stopped watching the episode (between 0 and 100). /// diff --git a/Kyoo.Common/Utility/Merger.cs b/Kyoo.Common/Utility/Merger.cs index cd860ea3..3c2d6247 100644 --- a/Kyoo.Common/Utility/Merger.cs +++ b/Kyoo.Common/Utility/Merger.cs @@ -22,12 +22,13 @@ namespace Kyoo /// The second enumerable to merge, if items from this list are equals to one from the first, they are not kept /// Equality function to compare items. If this is null, duplicated elements are kept /// The two list merged as an array - public static T[] MergeLists(IEnumerable first, - IEnumerable second, - Func isEqual = null) + [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] + public static T[] MergeLists([CanBeNull] IEnumerable first, + [CanBeNull] IEnumerable second, + [CanBeNull] Func isEqual = null) { if (first == null) - return second.ToArray(); + return second?.ToArray(); if (second == null) return first.ToArray(); if (isEqual == null) @@ -36,6 +37,98 @@ namespace Kyoo return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToArray(); } + /// + /// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept. + /// + /// The first dictionary to merge + /// The second dictionary to merge + /// The type of the keys in dictionaries + /// The type of values in the dictionaries + /// The first dictionary with the missing elements of . + /// + [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] + public static IDictionary MergeDictionaries([CanBeNull] IDictionary first, + [CanBeNull] IDictionary second) + { + return MergeDictionaries(first, second, out bool _); + } + + /// + /// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept. + /// + /// The first dictionary to merge + /// The second dictionary to merge + /// + /// true if a new items has been added to the dictionary, false otherwise. + /// + /// The type of the keys in dictionaries + /// The type of values in the dictionaries + /// The first dictionary with the missing elements of . + [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] + public static IDictionary MergeDictionaries([CanBeNull] IDictionary first, + [CanBeNull] IDictionary second, + out bool hasChanged) + { + if (first == null) + { + hasChanged = true; + return second; + } + + hasChanged = false; + if (second == null) + return first; + foreach ((T key, T2 value) in second) + hasChanged |= first.TryAdd(key, value); + return first; + } + + /// + /// Merge two dictionary, if the same key is found on both dictionary, the values of the second one is kept. + /// + /// + /// The only difference in this function compared to + /// + /// is the way is calculated and the order of the arguments. + /// + /// MergeDictionaries(first, second); + /// + /// will do the same thing as + /// + /// CompleteDictionaries(second, first, out bool _); + /// + /// + /// The first dictionary to merge + /// The second dictionary to merge + /// + /// true if a new items has been added to the dictionary, false otherwise. + /// + /// The type of the keys in dictionaries + /// The type of values in the dictionaries + /// + /// A dictionary with the missing elements of + /// set to those of . + /// + [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] + public static IDictionary CompleteDictionaries([CanBeNull] IDictionary first, + [CanBeNull] IDictionary second, + out bool hasChanged) + { + if (first == null) + { + hasChanged = true; + return second; + } + + hasChanged = false; + if (second == null) + return first; + hasChanged = second.Any(x => !x.Value.Equals(first[x.Key])); + foreach ((T key, T2 value) in first) + second.TryAdd(key, value); + return second; + } + /// /// Set every fields of first to those of second. Ignore fields marked with the attribute /// At the end, the OnMerge method of first will be called if first is a @@ -63,16 +156,34 @@ namespace Kyoo } /// - /// Set every default values of first to the value of second. ex: {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"}. + /// Set every non-default values of seconds to the corresponding property of second. + /// Dictionaries are handled like anonymous objects with a property per key/pair value + /// (see + /// + /// for more details). /// At the end, the OnMerge method of first will be called if first is a /// - /// The object to complete - /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. - /// Filter fields that will be merged + /// + /// This does the opposite of . + /// + /// + /// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "foo"} + /// + /// + /// The object to complete + /// + /// + /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. + /// + /// + /// Filter fields that will be merged + /// /// Fields of T will be completed /// /// If first is null - public static T Complete([NotNull] T first, [CanBeNull] T second, Func where = null) + public static T Complete([NotNull] T first, + [CanBeNull] T second, + [InstantHandle] Func where = null) { if (first == null) throw new ArgumentNullException(nameof(first)); @@ -93,7 +204,26 @@ namespace Kyoo object defaultValue = property.GetCustomAttribute()?.Value ?? property.PropertyType.GetClrDefault(); - if (value?.Equals(defaultValue) == false && value != property.GetValue(first)) + if (value?.Equals(defaultValue) != false || value.Equals(property.GetValue(first))) + continue; + if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) + { + Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) + .GenericTypeArguments; + object[] parameters = { + property.GetValue(first), + value, + false + }; + object newDictionary = Utility.RunGenericMethod( + typeof(Merger), + nameof(CompleteDictionaries), + dictionaryTypes, + parameters); + if ((bool)parameters[2]) + property.SetValue(first, newDictionary); + } + else property.SetValue(first, value); } @@ -103,17 +233,28 @@ namespace Kyoo } /// - /// An advanced function. /// This will set missing values of to the corresponding values of . - /// Enumerable will be merged (concatenated). + /// Enumerable will be merged (concatenated) and Dictionaries too. /// At the end, the OnMerge method of first will be called if first is a . /// - /// The object to complete - /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. + /// + /// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"} + /// + /// + /// The object to complete + /// + /// + /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. + /// + /// + /// Filter fields that will be merged + /// /// Fields of T will be merged /// [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static T Merge([CanBeNull] T first, [CanBeNull] T second) + public static T Merge([CanBeNull] T first, + [CanBeNull] T second, + [InstantHandle] Func where = null) { if (first == null) return second; @@ -125,6 +266,9 @@ namespace Kyoo .Where(x => x.CanRead && x.CanWrite && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); + if (where != null) + properties = properties.Where(where); + foreach (PropertyInfo property in properties) { object oldValue = property.GetValue(first); @@ -133,6 +277,23 @@ namespace Kyoo if (oldValue?.Equals(defaultValue) != false) property.SetValue(first, newValue); + else if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) + { + Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) + .GenericTypeArguments; + object[] parameters = { + oldValue, + newValue, + false + }; + object newDictionary = Utility.RunGenericMethod( + typeof(Merger), + nameof(MergeDictionaries), + dictionaryTypes, + parameters); + if ((bool)parameters[2]) + property.SetValue(first, newDictionary); + } else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) && property.PropertyType != typeof(string)) { diff --git a/Kyoo.Common/Utility/Utility.cs b/Kyoo.Common/Utility/Utility.cs index 2f7e14ad..72d25fd5 100644 --- a/Kyoo.Common/Utility/Utility.cs +++ b/Kyoo.Common/Utility/Utility.cs @@ -325,7 +325,7 @@ namespace Kyoo if (types.Length < 1) throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed."); MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args); - return (T)method.MakeGenericMethod(types).Invoke(null, args.ToArray()); + return (T)method.MakeGenericMethod(types).Invoke(null, args); } /// diff --git a/Kyoo.CommonAPI/DatabaseContext.cs b/Kyoo.CommonAPI/DatabaseContext.cs index 36cdc47b..ad96ff78 100644 --- a/Kyoo.CommonAPI/DatabaseContext.cs +++ b/Kyoo.CommonAPI/DatabaseContext.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -60,6 +62,7 @@ namespace Kyoo /// All providers of Kyoo. See . /// public DbSet Providers { get; set; } + /// /// The list of registered users. /// @@ -84,28 +87,34 @@ namespace Kyoo public DbSet LibraryItems { get; set; } /// - /// Get all metadataIDs (ExternalIDs) of a given resource. See . + /// Get all metadataIDs (ExternalIDs) of a given resource. See . /// /// The metadata of this type will be returned. /// A queryable of metadata ids for a type. - public DbSet> MetadataIds() - where T : class, IResource + public DbSet MetadataIds() + where T : class, IMetadata { - return Set>(); + return Set(MetadataName()); } - + /// - /// Get a generic link between two resource types. + /// Add a many to many link between two resources. /// /// Types are order dependant. You can't inverse the order. Please always put the owner first. + /// The ID of the first resource. + /// The ID of the second resource. /// The first resource type of the relation. It is the owner of the second /// The second resource type of the relation. It is the contained resource. - /// All links between the two types. - public DbSet> Links() + public async Task AddLinks(int first, int second) where T1 : class, IResource where T2 : class, IResource { - return Set>(); + await Set>(LinkName()) + .AddAsync(new Dictionary + { + [LinkNameFk()] = first, + [LinkNameFk()] = second + }); } @@ -122,6 +131,32 @@ namespace Kyoo : base(options) { } + /// + /// Get the name of the metadata table of the given type. + /// + /// The type related to the metadata + /// The name of the table containing the metadata. + protected abstract string MetadataName() + where T : IMetadata; + + /// + /// Get the name of the link table of the two given types. + /// + /// The owner type of the relation + /// The child type of the relation + /// The name of the table containing the links. + protected abstract string LinkName() + where T : IResource + where T2 : IResource; + + /// + /// Get the name of a link's foreign key. + /// + /// The type that will be accessible via the navigation + /// The name of the foreign key for the given resource. + protected abstract string LinkNameFk() + where T : IResource; + /// /// Set basic configurations (like preventing query tracking) /// @@ -132,6 +167,58 @@ namespace Kyoo optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); } + /// + /// Build the metadata model for the given type. + /// + /// The database model builder + /// The type to add metadata to. + private void _HasMetadata(ModelBuilder modelBuilder) + where T : class, IMetadata + { + modelBuilder.SharedTypeEntity(MetadataName()) + .HasKey(MetadataID.PrimaryKey); + + modelBuilder.SharedTypeEntity(MetadataName()) + .HasOne() + .WithMany(x => x.ExternalIDs) + .HasForeignKey(x => x.ResourceID) + .OnDelete(DeleteBehavior.Cascade); + } + + /// + /// Create a many to many relationship between the two entities. + /// The resulting relationship will have an available method. + /// + /// The database model builder + /// The first navigation expression from T to T2 + /// The second navigation expression from T2 to T + /// The owning type of the relationship + /// The owned type of the relationship + private void _HasManyToMany(ModelBuilder modelBuilder, + Expression>> firstNavigation, + Expression>> secondNavigation) + where T : class, IResource + where T2 : class, IResource + { + modelBuilder.Entity() + .HasMany(secondNavigation) + .WithMany(firstNavigation) + .UsingEntity>( + LinkName(), + x => x + .HasOne() + .WithMany() + .HasForeignKey(LinkNameFk()) + .OnDelete(DeleteBehavior.Cascade), + x => x + .HasOne() + .WithMany() + .HasForeignKey(LinkNameFk()) + .OnDelete(DeleteBehavior.Cascade) + ); + } + + /// /// Set database parameters to support every types of Kyoo. /// @@ -140,6 +227,9 @@ namespace Kyoo { base.OnModelCreating(modelBuilder); + modelBuilder.Entity() + .Ignore(x => x.ForPeople); + modelBuilder.Entity() .HasMany(x => x.Seasons) .WithOne(x => x.Show) @@ -162,117 +252,26 @@ namespace Kyoo .WithMany(x => x.Shows) .OnDelete(DeleteBehavior.SetNull); - modelBuilder.Entity() - .HasMany(x => x.Libraries) - .WithMany(x => x.Providers) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.ProviderLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.LibraryLinks), - y => y.HasKey(Link.PrimaryKey)); - - modelBuilder.Entity() - .HasMany(x => x.Libraries) - .WithMany(x => x.Collections) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.CollectionLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.LibraryLinks), - y => y.HasKey(Link.PrimaryKey)); - - modelBuilder.Entity() - .HasMany(x => x.Libraries) - .WithMany(x => x.Shows) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.ShowLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.LibraryLinks), - y => y.HasKey(Link.PrimaryKey)); - - modelBuilder.Entity() - .HasMany(x => x.Collections) - .WithMany(x => x.Shows) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.ShowLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.CollectionLinks), - y => y.HasKey(Link.PrimaryKey)); - - modelBuilder.Entity() - .HasMany(x => x.Shows) - .WithMany(x => x.Genres) - .UsingEntity>( - y => y - .HasOne(x => x.First) - .WithMany(x => x.GenreLinks), - y => y - .HasOne(x => x.Second) - .WithMany(x => x.ShowLinks), - y => y.HasKey(Link.PrimaryKey)); + _HasManyToMany(modelBuilder, x => x.Providers, x => x.Libraries); + _HasManyToMany(modelBuilder, x => x.Collections, x => x.Libraries); + _HasManyToMany(modelBuilder, x => x.Shows, x => x.Libraries); + _HasManyToMany(modelBuilder, x => x.Shows, x => x.Collections); + _HasManyToMany(modelBuilder, x => x.Genres, x => x.Shows); modelBuilder.Entity() .HasMany(x => x.Watched) - .WithMany("users") - .UsingEntity>( - y => y - .HasOne(x => x.Second) - .WithMany(), - y => y - .HasOne(x => x.First) - .WithMany(x => x.ShowLinks), - y => y.HasKey(Link.PrimaryKey)); + .WithMany("Users") + .UsingEntity(x => x.ToTable(LinkName())); - modelBuilder.Entity>() - .HasKey(MetadataID.PrimaryKey); - modelBuilder.Entity>() - .HasOne(x => x.First) - .WithMany(x => x.ExternalIDs) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>() - .HasKey(MetadataID.PrimaryKey); - modelBuilder.Entity>() - .HasOne(x => x.First) - .WithMany(x => x.ExternalIDs) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>() - .HasKey(MetadataID.PrimaryKey); - modelBuilder.Entity>() - .HasOne(x => x.First) - .WithMany(x => x.ExternalIDs) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>() - .HasKey(MetadataID.PrimaryKey); - modelBuilder.Entity>() - .HasOne(x => x.First) - .WithMany(x => x.ExternalIDs) - .OnDelete(DeleteBehavior.Cascade); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); - - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity>().HasOne(x => x.Second).WithMany() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasKey(x => new {First = x.FirstID, Second = x.SecondID}); + .HasKey(x => new { User = x.UserID, Episode = x.EpisodeID }); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); @@ -505,6 +504,23 @@ namespace Kyoo } } + /// + /// Return the first resource with the given slug that is currently tracked by this context. + /// This allow one to limit redundant calls to during the + /// same transaction and prevent fails from EF when two same entities are being tracked. + /// + /// The slug of the resource to check + /// The type of entity to check + /// The local entity representing the resource with the given slug if it exists or null. + [CanBeNull] + public T LocalEntity(string slug) + where T : class, IResource + { + return ChangeTracker.Entries() + .FirstOrDefault(x => x.Entity.Slug == slug) + ?.Entity; + } + /// /// Check if the exception is a duplicated exception. /// @@ -517,14 +533,12 @@ namespace Kyoo /// private void DiscardChanges() { - foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged - && x.State != EntityState.Detached)) + foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Detached)) { entry.State = EntityState.Detached; } } - - + /// /// Perform a case insensitive like operation. /// diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 0187e0dd..67a75e12 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -234,16 +234,23 @@ namespace Kyoo.Controllers finally { Database.ChangeTracker.LazyLoadingEnabled = lazyLoading; + Database.ChangeTracker.Clear(); } } /// /// An overridable method to edit relation of a resource. /// - /// The non edited resource - /// The new version of . This item will be saved on the databse and replace - /// A boolean to indicate if all values of resource should be discarded or not. - /// + /// + /// The non edited resource + /// + /// + /// The new version of . + /// This item will be saved on the database and replace + /// + /// + /// A boolean to indicate if all values of resource should be discarded or not. + /// protected virtual Task EditRelations(T resource, T changed, bool resetOld) { return Validate(resource); @@ -254,7 +261,9 @@ namespace Kyoo.Controllers /// It is also called on the default implementation of /// /// The resource that will be saved - /// You can throw this if the resource is illegal and should not be saved. + /// + /// You can throw this if the resource is illegal and should not be saved. + /// protected virtual Task Validate(T resource) { if (typeof(T).GetProperty(nameof(resource.Slug))!.GetCustomAttribute() != null) diff --git a/Kyoo.Postgresql/Migrations/20210723224326_Initial.Designer.cs b/Kyoo.Postgresql/Migrations/20210801171613_Initial.Designer.cs similarity index 70% rename from Kyoo.Postgresql/Migrations/20210723224326_Initial.Designer.cs rename to Kyoo.Postgresql/Migrations/20210801171613_Initial.Designer.cs index 3016a040..20b6f7bd 100644 --- a/Kyoo.Postgresql/Migrations/20210723224326_Initial.Designer.cs +++ b/Kyoo.Postgresql/Migrations/20210801171613_Initial.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20210723224326_Initial")] + [Migration("20210801171613_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -34,6 +34,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); @@ -42,10 +46,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -77,6 +77,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("episode_number"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -106,10 +110,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("slug"); - b.Property("Thumb") - .HasColumnType("text") - .HasColumnName("thumb"); - b.Property("Title") .HasColumnType("text") .HasColumnName("title"); @@ -200,14 +200,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .HasColumnType("text") .HasColumnName("slug"); @@ -234,228 +234,6 @@ namespace Kyoo.Postgresql.Migrations b.ToView("library_items"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_collection_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_collection_show_second_id"); - - b.ToTable("link_collection_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_collection"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_collection_second_id"); - - b.ToTable("link_library_collection"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_provider"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_provider_second_id"); - - b.ToTable("link_library_provider"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_show_second_id"); - - b.ToTable("link_library_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_show_genre"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_show_genre_second_id"); - - b.ToTable("link_show_genre"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_user_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_user_show_second_id"); - - b.ToTable("link_user_show"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_episode"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_episode_second_id"); - - b.ToTable("metadata_id_episode"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_people"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_people_second_id"); - - b.ToTable("metadata_id_people"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_season"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_season_second_id"); - - b.ToTable("metadata_id_season"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_show_second_id"); - - b.ToTable("metadata_id_show"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -464,14 +242,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -495,10 +273,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ForPeople") - .HasColumnType("boolean") - .HasColumnName("for_people"); - b.Property("PeopleID") .HasColumnType("integer") .HasColumnName("people_id"); @@ -535,13 +309,9 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); - - b.Property("LogoExtension") - .HasColumnType("text") - .HasColumnName("logo_extension"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); b.Property("Name") .HasColumnType("text") @@ -574,14 +344,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_date"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("SeasonNumber") .HasColumnType("integer") .HasColumnName("season_number"); @@ -629,22 +399,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text[]") .HasColumnName("aliases"); - b.Property("Backdrop") - .HasColumnType("text") - .HasColumnName("backdrop"); - b.Property("EndAir") .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("IsMovie") .HasColumnType("boolean") .HasColumnName("is_movie"); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); - b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -653,10 +419,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("path"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -678,10 +440,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("title"); - b.Property("TrailerUrl") - .HasColumnType("text") - .HasColumnName("trailer_url"); - b.HasKey("ID") .HasName("pk_shows"); @@ -805,6 +563,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("jsonb") .HasColumnName("extra_data"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Password") .HasColumnType("text") .HasColumnName("password"); @@ -834,27 +596,303 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("integer") - .HasColumnName("first_id"); + .HasColumnName("user_id"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("integer") - .HasColumnName("second_id"); + .HasColumnName("episode_id"); b.Property("WatchedPercentage") .HasColumnType("integer") .HasColumnName("watched_percentage"); - b.HasKey("FirstID", "SecondID") + b.HasKey("UserID", "EpisodeID") .HasName("pk_watched_episodes"); - b.HasIndex("SecondID") - .HasDatabaseName("ix_watched_episodes_second_id"); + b.HasIndex("EpisodeID") + .HasDatabaseName("ix_watched_episodes_episode_id"); b.ToTable("watched_episodes"); }); + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("integer") + .HasColumnName("users_id"); + + b.Property("WatchedID") + .HasColumnType("integer") + .HasColumnName("watched_id"); + + b.HasKey("UsersID", "WatchedID") + .HasName("pk_link_user_show"); + + b.HasIndex("WatchedID") + .HasDatabaseName("ix_link_user_show_watched_id"); + + b.ToTable("link_user_show"); + }); + + modelBuilder.Entity("collection_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_collection_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_collection_metadata_id_provider_id"); + + b.ToTable("collection_metadata_id"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_episode_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_episode_metadata_id_provider_id"); + + b.ToTable("episode_metadata_id"); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("collection_id", "show_id") + .HasName("pk_link_collection_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_collection_show_show_id"); + + b.ToTable("link_collection_show"); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.HasKey("collection_id", "library_id") + .HasName("pk_link_library_collection"); + + b.HasIndex("library_id") + .HasDatabaseName("ix_link_library_collection_library_id"); + + b.ToTable("link_library_collection"); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("provider_id") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.HasKey("library_id", "provider_id") + .HasName("pk_link_library_provider"); + + b.HasIndex("provider_id") + .HasDatabaseName("ix_link_library_provider_provider_id"); + + b.ToTable("link_library_provider"); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("library_id", "show_id") + .HasName("pk_link_library_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_library_show_show_id"); + + b.ToTable("link_library_show"); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.Property("genre_id") + .HasColumnType("integer") + .HasColumnName("genre_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("genre_id", "show_id") + .HasName("pk_link_show_genre"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_show_genre_show_id"); + + b.ToTable("link_show_genre"); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_people_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_people_metadata_id_provider_id"); + + b.ToTable("people_metadata_id"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_season_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_season_metadata_id_provider_id"); + + b.ToTable("season_metadata_id"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_show_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_show_metadata_id_provider_id"); + + b.ToTable("show_metadata_id"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_studio_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_studio_metadata_id_provider_id"); + + b.ToTable("studio_metadata_id"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -875,216 +913,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_collection_show_collections_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_collection_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_collection_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_collection_collections_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_provider_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_provider_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_show_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_show_genre_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_show_genre_genres_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_user_show_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_user_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_episode_episodes_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_episode_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_people_people_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_people_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_season_seasons_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_season_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_show_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_show_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -1143,30 +971,242 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .HasConstraintName("fk_watched_episodes_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_watched_episodes_episodes_second_id") + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_watched_episodes_episodes_episode_id") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .HasConstraintName("fk_watched_episodes_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .HasConstraintName("fk_link_user_show_users_users_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .HasConstraintName("fk_link_user_show_shows_watched_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("collection_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_collection_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_collection_metadata_id_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_episode_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_episode_metadata_id_episodes_episode_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_collection_show_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_collection_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_library_collection_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_collection_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_provider_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("provider_id") + .HasConstraintName("fk_link_library_provider_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_show_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_library_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("genre_id") + .HasConstraintName("fk_link_show_genre_genres_genre_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_show_genre_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_people_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_people_metadata_id_people_people_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_season_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_season_metadata_id_seasons_season_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_show_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_show_metadata_id_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_studio_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_studio_metadata_id_studios_studio_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); }); modelBuilder.Entity("Kyoo.Models.Collection", b => { - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); + b.Navigation("ExternalIDs"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -1176,20 +1216,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -1197,11 +1223,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -1211,16 +1232,10 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1228,14 +1243,14 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.Postgresql/Migrations/20210723224326_Initial.cs b/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs similarity index 72% rename from Kyoo.Postgresql/Migrations/20210723224326_Initial.cs rename to Kyoo.Postgresql/Migrations/20210801171613_Initial.cs index 2ba22c6a..395d9e27 100644 --- a/Kyoo.Postgresql/Migrations/20210723224326_Initial.cs +++ b/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs @@ -23,7 +23,7 @@ namespace Kyoo.Postgresql.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), slug = table.Column(type: "text", nullable: false), name = table.Column(type: "text", nullable: true), - poster = table.Column(type: "text", nullable: true), + images = table.Column>(type: "jsonb", nullable: true), overview = table.Column(type: "text", nullable: true) }, constraints: table => @@ -68,7 +68,7 @@ namespace Kyoo.Postgresql.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), slug = table.Column(type: "text", nullable: false), name = table.Column(type: "text", nullable: true), - poster = table.Column(type: "text", nullable: true) + images = table.Column>(type: "jsonb", nullable: true) }, constraints: table => { @@ -83,8 +83,7 @@ namespace Kyoo.Postgresql.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), slug = table.Column(type: "text", nullable: false), name = table.Column(type: "text", nullable: true), - logo = table.Column(type: "text", nullable: true), - logo_extension = table.Column(type: "text", nullable: true) + images = table.Column>(type: "jsonb", nullable: true) }, constraints: table => { @@ -116,7 +115,8 @@ namespace Kyoo.Postgresql.Migrations email = table.Column(type: "text", nullable: true), password = table.Column(type: "text", nullable: true), permissions = table.Column(type: "text[]", nullable: true), - extra_data = table.Column>(type: "jsonb", nullable: true) + extra_data = table.Column>(type: "jsonb", nullable: true), + images = table.Column>(type: "jsonb", nullable: true) }, constraints: table => { @@ -127,71 +127,97 @@ namespace Kyoo.Postgresql.Migrations name: "link_library_collection", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + collection_id = table.Column(type: "integer", nullable: false), + library_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_library_collection", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_library_collection", x => new { x.collection_id, x.library_id }); table.ForeignKey( - name: "fk_link_library_collection_collections_second_id", - column: x => x.second_id, + name: "fk_link_library_collection_collections_collection_id", + column: x => x.collection_id, principalTable: "collections", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_library_collection_libraries_first_id", - column: x => x.first_id, + name: "fk_link_library_collection_libraries_library_id", + column: x => x.library_id, principalTable: "libraries", principalColumn: "id", onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "collection_metadata_id", + columns: table => new + { + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), + data_id = table.Column(type: "text", nullable: true), + link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_collection_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_collection_metadata_id_collections_collection_id", + column: x => x.resource_id, + principalTable: "collections", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_collection_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "link_library_provider", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + library_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_library_provider", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_library_provider", x => new { x.library_id, x.provider_id }); table.ForeignKey( - name: "fk_link_library_provider_libraries_first_id", - column: x => x.first_id, + name: "fk_link_library_provider_libraries_library_id", + column: x => x.library_id, principalTable: "libraries", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_library_provider_providers_second_id", - column: x => x.second_id, + name: "fk_link_library_provider_providers_provider_id", + column: x => x.provider_id, principalTable: "providers", principalColumn: "id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "metadata_id_people", + name: "people_metadata_id", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false), + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), data_id = table.Column(type: "text", nullable: true), link = table.Column(type: "text", nullable: true) }, constraints: table => { - table.PrimaryKey("pk_metadata_id_people", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_people_metadata_id", x => new { x.resource_id, x.provider_id }); table.ForeignKey( - name: "fk_metadata_id_people_people_first_id", - column: x => x.first_id, + name: "fk_people_metadata_id_people_people_id", + column: x => x.resource_id, principalTable: "people", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_metadata_id_people_providers_second_id", - column: x => x.second_id, + name: "fk_people_metadata_id_providers_provider_id", + column: x => x.provider_id, principalTable: "providers", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -209,12 +235,9 @@ namespace Kyoo.Postgresql.Migrations path = table.Column(type: "text", nullable: true), overview = table.Column(type: "text", nullable: true), status = table.Column(type: "status", nullable: false), - trailer_url = table.Column(type: "text", nullable: true), start_air = table.Column(type: "timestamp without time zone", nullable: true), end_air = table.Column(type: "timestamp without time zone", nullable: true), - poster = table.Column(type: "text", nullable: true), - logo = table.Column(type: "text", nullable: true), - backdrop = table.Column(type: "text", nullable: true), + images = table.Column>(type: "jsonb", nullable: true), is_movie = table.Column(type: "boolean", nullable: false), studio_id = table.Column(type: "integer", nullable: true) }, @@ -230,24 +253,50 @@ namespace Kyoo.Postgresql.Migrations }); migrationBuilder.CreateTable( - name: "link_collection_show", + name: "studio_metadata_id", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), + data_id = table.Column(type: "text", nullable: true), + link = table.Column(type: "text", nullable: true) }, constraints: table => { - table.PrimaryKey("pk_link_collection_show", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_studio_metadata_id", x => new { x.resource_id, x.provider_id }); table.ForeignKey( - name: "fk_link_collection_show_collections_first_id", - column: x => x.first_id, + name: "fk_studio_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_studio_metadata_id_studios_studio_id", + column: x => x.resource_id, + principalTable: "studios", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_collection_show", + columns: table => new + { + collection_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id }); + table.ForeignKey( + name: "fk_link_collection_show_collections_collection_id", + column: x => x.collection_id, principalTable: "collections", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_collection_show_shows_second_id", - column: x => x.second_id, + name: "fk_link_collection_show_shows_show_id", + column: x => x.show_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -257,21 +306,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_library_show", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + library_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_library_show", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_library_show", x => new { x.library_id, x.show_id }); table.ForeignKey( - name: "fk_link_library_show_libraries_first_id", - column: x => x.first_id, + name: "fk_link_library_show_libraries_library_id", + column: x => x.library_id, principalTable: "libraries", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_library_show_shows_second_id", - column: x => x.second_id, + name: "fk_link_library_show_shows_show_id", + column: x => x.show_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -281,21 +330,21 @@ namespace Kyoo.Postgresql.Migrations name: "link_show_genre", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + genre_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_show_genre", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_show_genre", x => new { x.genre_id, x.show_id }); table.ForeignKey( - name: "fk_link_show_genre_genres_second_id", - column: x => x.second_id, + name: "fk_link_show_genre_genres_genre_id", + column: x => x.genre_id, principalTable: "genres", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_show_genre_shows_first_id", - column: x => x.first_id, + name: "fk_link_show_genre_shows_show_id", + column: x => x.show_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -305,59 +354,32 @@ namespace Kyoo.Postgresql.Migrations name: "link_user_show", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false) + users_id = table.Column(type: "integer", nullable: false), + watched_id = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_link_user_show", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id }); table.ForeignKey( - name: "fk_link_user_show_shows_second_id", - column: x => x.second_id, + name: "fk_link_user_show_shows_watched_id", + column: x => x.watched_id, principalTable: "shows", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_link_user_show_users_first_id", - column: x => x.first_id, + name: "fk_link_user_show_users_users_id", + column: x => x.users_id, principalTable: "users", principalColumn: "id", onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "metadata_id_show", - columns: table => new - { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false), - data_id = table.Column(type: "text", nullable: true), - link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_metadata_id_show", x => new { x.first_id, x.second_id }); - table.ForeignKey( - name: "fk_metadata_id_show_providers_second_id", - column: x => x.second_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_metadata_id_show_shows_first_id", - column: x => x.first_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "people_roles", columns: table => new { id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - for_people = table.Column(type: "boolean", nullable: false), people_id = table.Column(type: "integer", nullable: false), show_id = table.Column(type: "integer", nullable: false), type = table.Column(type: "text", nullable: true), @@ -393,7 +415,7 @@ namespace Kyoo.Postgresql.Migrations overview = table.Column(type: "text", nullable: true), start_date = table.Column(type: "timestamp without time zone", nullable: true), end_date = table.Column(type: "timestamp without time zone", nullable: true), - poster = table.Column(type: "text", nullable: true) + images = table.Column>(type: "jsonb", nullable: true) }, constraints: table => { @@ -406,6 +428,32 @@ namespace Kyoo.Postgresql.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "show_metadata_id", + columns: table => new + { + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), + data_id = table.Column(type: "text", nullable: true), + link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_show_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_show_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_show_metadata_id_shows_show_id", + column: x => x.resource_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "episodes", columns: table => new @@ -419,7 +467,7 @@ namespace Kyoo.Postgresql.Migrations episode_number = table.Column(type: "integer", nullable: true), absolute_number = table.Column(type: "integer", nullable: true), path = table.Column(type: "text", nullable: true), - thumb = table.Column(type: "text", nullable: true), + images = table.Column>(type: "jsonb", nullable: true), title = table.Column(type: "text", nullable: true), overview = table.Column(type: "text", nullable: true), release_date = table.Column(type: "timestamp without time zone", nullable: true) @@ -442,52 +490,52 @@ namespace Kyoo.Postgresql.Migrations }); migrationBuilder.CreateTable( - name: "metadata_id_season", + name: "season_metadata_id", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false), + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), data_id = table.Column(type: "text", nullable: true), link = table.Column(type: "text", nullable: true) }, constraints: table => { - table.PrimaryKey("pk_metadata_id_season", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_season_metadata_id", x => new { x.resource_id, x.provider_id }); table.ForeignKey( - name: "fk_metadata_id_season_providers_second_id", - column: x => x.second_id, + name: "fk_season_metadata_id_providers_provider_id", + column: x => x.provider_id, principalTable: "providers", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_metadata_id_season_seasons_first_id", - column: x => x.first_id, + name: "fk_season_metadata_id_seasons_season_id", + column: x => x.resource_id, principalTable: "seasons", principalColumn: "id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "metadata_id_episode", + name: "episode_metadata_id", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false), + resource_id = table.Column(type: "integer", nullable: false), + provider_id = table.Column(type: "integer", nullable: false), data_id = table.Column(type: "text", nullable: true), link = table.Column(type: "text", nullable: true) }, constraints: table => { - table.PrimaryKey("pk_metadata_id_episode", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_episode_metadata_id", x => new { x.resource_id, x.provider_id }); table.ForeignKey( - name: "fk_metadata_id_episode_episodes_first_id", - column: x => x.first_id, + name: "fk_episode_metadata_id_episodes_episode_id", + column: x => x.resource_id, principalTable: "episodes", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_metadata_id_episode_providers_second_id", - column: x => x.second_id, + name: "fk_episode_metadata_id_providers_provider_id", + column: x => x.provider_id, principalTable: "providers", principalColumn: "id", onDelete: ReferentialAction.Cascade); @@ -526,33 +574,43 @@ namespace Kyoo.Postgresql.Migrations name: "watched_episodes", columns: table => new { - first_id = table.Column(type: "integer", nullable: false), - second_id = table.Column(type: "integer", nullable: false), + user_id = table.Column(type: "integer", nullable: false), + episode_id = table.Column(type: "integer", nullable: false), watched_percentage = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("pk_watched_episodes", x => new { x.first_id, x.second_id }); + table.PrimaryKey("pk_watched_episodes", x => new { x.user_id, x.episode_id }); table.ForeignKey( - name: "fk_watched_episodes_episodes_second_id", - column: x => x.second_id, + name: "fk_watched_episodes_episodes_episode_id", + column: x => x.episode_id, principalTable: "episodes", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_watched_episodes_users_first_id", - column: x => x.first_id, + name: "fk_watched_episodes_users_user_id", + column: x => x.user_id, principalTable: "users", principalColumn: "id", onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateIndex( + name: "ix_collection_metadata_id_provider_id", + table: "collection_metadata_id", + column: "provider_id"); + migrationBuilder.CreateIndex( name: "ix_collections_slug", table: "collections", column: "slug", unique: true); + migrationBuilder.CreateIndex( + name: "ix_episode_metadata_id_provider_id", + table: "episode_metadata_id", + column: "provider_id"); + migrationBuilder.CreateIndex( name: "ix_episodes_season_id", table: "episodes", @@ -583,54 +641,34 @@ namespace Kyoo.Postgresql.Migrations unique: true); migrationBuilder.CreateIndex( - name: "ix_link_collection_show_second_id", + name: "ix_link_collection_show_show_id", table: "link_collection_show", - column: "second_id"); + column: "show_id"); migrationBuilder.CreateIndex( - name: "ix_link_library_collection_second_id", + name: "ix_link_library_collection_library_id", table: "link_library_collection", - column: "second_id"); + column: "library_id"); migrationBuilder.CreateIndex( - name: "ix_link_library_provider_second_id", + name: "ix_link_library_provider_provider_id", table: "link_library_provider", - column: "second_id"); + column: "provider_id"); migrationBuilder.CreateIndex( - name: "ix_link_library_show_second_id", + name: "ix_link_library_show_show_id", table: "link_library_show", - column: "second_id"); + column: "show_id"); migrationBuilder.CreateIndex( - name: "ix_link_show_genre_second_id", + name: "ix_link_show_genre_show_id", table: "link_show_genre", - column: "second_id"); + column: "show_id"); migrationBuilder.CreateIndex( - name: "ix_link_user_show_second_id", + name: "ix_link_user_show_watched_id", table: "link_user_show", - column: "second_id"); - - migrationBuilder.CreateIndex( - name: "ix_metadata_id_episode_second_id", - table: "metadata_id_episode", - column: "second_id"); - - migrationBuilder.CreateIndex( - name: "ix_metadata_id_people_second_id", - table: "metadata_id_people", - column: "second_id"); - - migrationBuilder.CreateIndex( - name: "ix_metadata_id_season_second_id", - table: "metadata_id_season", - column: "second_id"); - - migrationBuilder.CreateIndex( - name: "ix_metadata_id_show_second_id", - table: "metadata_id_show", - column: "second_id"); + column: "watched_id"); migrationBuilder.CreateIndex( name: "ix_people_slug", @@ -638,6 +676,11 @@ namespace Kyoo.Postgresql.Migrations column: "slug", unique: true); + migrationBuilder.CreateIndex( + name: "ix_people_metadata_id_provider_id", + table: "people_metadata_id", + column: "provider_id"); + migrationBuilder.CreateIndex( name: "ix_people_roles_people_id", table: "people_roles", @@ -654,6 +697,11 @@ namespace Kyoo.Postgresql.Migrations column: "slug", unique: true); + migrationBuilder.CreateIndex( + name: "ix_season_metadata_id_provider_id", + table: "season_metadata_id", + column: "provider_id"); + migrationBuilder.CreateIndex( name: "ix_seasons_show_id_season_number", table: "seasons", @@ -666,6 +714,11 @@ namespace Kyoo.Postgresql.Migrations column: "slug", unique: true); + migrationBuilder.CreateIndex( + name: "ix_show_metadata_id_provider_id", + table: "show_metadata_id", + column: "provider_id"); + migrationBuilder.CreateIndex( name: "ix_shows_slug", table: "shows", @@ -677,6 +730,11 @@ namespace Kyoo.Postgresql.Migrations table: "shows", column: "studio_id"); + migrationBuilder.CreateIndex( + name: "ix_studio_metadata_id_provider_id", + table: "studio_metadata_id", + column: "provider_id"); + migrationBuilder.CreateIndex( name: "ix_studios_slug", table: "studios", @@ -702,13 +760,19 @@ namespace Kyoo.Postgresql.Migrations unique: true); migrationBuilder.CreateIndex( - name: "ix_watched_episodes_second_id", + name: "ix_watched_episodes_episode_id", table: "watched_episodes", - column: "second_id"); + column: "episode_id"); } protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "collection_metadata_id"); + + migrationBuilder.DropTable( + name: "episode_metadata_id"); + migrationBuilder.DropTable( name: "link_collection_show"); @@ -728,20 +792,20 @@ namespace Kyoo.Postgresql.Migrations name: "link_user_show"); migrationBuilder.DropTable( - name: "metadata_id_episode"); - - migrationBuilder.DropTable( - name: "metadata_id_people"); - - migrationBuilder.DropTable( - name: "metadata_id_season"); - - migrationBuilder.DropTable( - name: "metadata_id_show"); + name: "people_metadata_id"); migrationBuilder.DropTable( name: "people_roles"); + migrationBuilder.DropTable( + name: "season_metadata_id"); + + migrationBuilder.DropTable( + name: "show_metadata_id"); + + migrationBuilder.DropTable( + name: "studio_metadata_id"); + migrationBuilder.DropTable( name: "tracks"); @@ -758,10 +822,10 @@ namespace Kyoo.Postgresql.Migrations name: "genres"); migrationBuilder.DropTable( - name: "providers"); + name: "people"); migrationBuilder.DropTable( - name: "people"); + name: "providers"); migrationBuilder.DropTable( name: "episodes"); diff --git a/Kyoo.Postgresql/Migrations/20210723224335_Triggers.Designer.cs b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.Designer.cs similarity index 70% rename from Kyoo.Postgresql/Migrations/20210723224335_Triggers.Designer.cs rename to Kyoo.Postgresql/Migrations/20210801171641_Triggers.Designer.cs index d377a09f..fd13824a 100644 --- a/Kyoo.Postgresql/Migrations/20210723224335_Triggers.Designer.cs +++ b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20210723224335_Triggers")] + [Migration("20210801171641_Triggers")] partial class Triggers { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -34,6 +34,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); @@ -42,10 +46,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -77,6 +77,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("episode_number"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -106,10 +110,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("slug"); - b.Property("Thumb") - .HasColumnType("text") - .HasColumnName("thumb"); - b.Property("Title") .HasColumnType("text") .HasColumnName("title"); @@ -200,14 +200,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .HasColumnType("text") .HasColumnName("slug"); @@ -234,228 +234,6 @@ namespace Kyoo.Postgresql.Migrations b.ToView("library_items"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_collection_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_collection_show_second_id"); - - b.ToTable("link_collection_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_collection"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_collection_second_id"); - - b.ToTable("link_library_collection"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_provider"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_provider_second_id"); - - b.ToTable("link_library_provider"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_show_second_id"); - - b.ToTable("link_library_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_show_genre"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_show_genre_second_id"); - - b.ToTable("link_show_genre"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_user_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_user_show_second_id"); - - b.ToTable("link_user_show"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_episode"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_episode_second_id"); - - b.ToTable("metadata_id_episode"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_people"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_people_second_id"); - - b.ToTable("metadata_id_people"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_season"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_season_second_id"); - - b.ToTable("metadata_id_season"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_show_second_id"); - - b.ToTable("metadata_id_show"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -464,14 +242,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -495,10 +273,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ForPeople") - .HasColumnType("boolean") - .HasColumnName("for_people"); - b.Property("PeopleID") .HasColumnType("integer") .HasColumnName("people_id"); @@ -535,13 +309,9 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); - - b.Property("LogoExtension") - .HasColumnType("text") - .HasColumnName("logo_extension"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); b.Property("Name") .HasColumnType("text") @@ -574,14 +344,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_date"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("SeasonNumber") .HasColumnType("integer") .HasColumnName("season_number"); @@ -629,22 +399,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text[]") .HasColumnName("aliases"); - b.Property("Backdrop") - .HasColumnType("text") - .HasColumnName("backdrop"); - b.Property("EndAir") .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("IsMovie") .HasColumnType("boolean") .HasColumnName("is_movie"); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); - b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -653,10 +419,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("path"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -678,10 +440,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("title"); - b.Property("TrailerUrl") - .HasColumnType("text") - .HasColumnName("trailer_url"); - b.HasKey("ID") .HasName("pk_shows"); @@ -805,6 +563,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("jsonb") .HasColumnName("extra_data"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Password") .HasColumnType("text") .HasColumnName("password"); @@ -834,27 +596,303 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("integer") - .HasColumnName("first_id"); + .HasColumnName("user_id"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("integer") - .HasColumnName("second_id"); + .HasColumnName("episode_id"); b.Property("WatchedPercentage") .HasColumnType("integer") .HasColumnName("watched_percentage"); - b.HasKey("FirstID", "SecondID") + b.HasKey("UserID", "EpisodeID") .HasName("pk_watched_episodes"); - b.HasIndex("SecondID") - .HasDatabaseName("ix_watched_episodes_second_id"); + b.HasIndex("EpisodeID") + .HasDatabaseName("ix_watched_episodes_episode_id"); b.ToTable("watched_episodes"); }); + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("integer") + .HasColumnName("users_id"); + + b.Property("WatchedID") + .HasColumnType("integer") + .HasColumnName("watched_id"); + + b.HasKey("UsersID", "WatchedID") + .HasName("pk_link_user_show"); + + b.HasIndex("WatchedID") + .HasDatabaseName("ix_link_user_show_watched_id"); + + b.ToTable("link_user_show"); + }); + + modelBuilder.Entity("collection_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_collection_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_collection_metadata_id_provider_id"); + + b.ToTable("collection_metadata_id"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_episode_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_episode_metadata_id_provider_id"); + + b.ToTable("episode_metadata_id"); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("collection_id", "show_id") + .HasName("pk_link_collection_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_collection_show_show_id"); + + b.ToTable("link_collection_show"); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.HasKey("collection_id", "library_id") + .HasName("pk_link_library_collection"); + + b.HasIndex("library_id") + .HasDatabaseName("ix_link_library_collection_library_id"); + + b.ToTable("link_library_collection"); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("provider_id") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.HasKey("library_id", "provider_id") + .HasName("pk_link_library_provider"); + + b.HasIndex("provider_id") + .HasDatabaseName("ix_link_library_provider_provider_id"); + + b.ToTable("link_library_provider"); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("library_id", "show_id") + .HasName("pk_link_library_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_library_show_show_id"); + + b.ToTable("link_library_show"); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.Property("genre_id") + .HasColumnType("integer") + .HasColumnName("genre_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("genre_id", "show_id") + .HasName("pk_link_show_genre"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_show_genre_show_id"); + + b.ToTable("link_show_genre"); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_people_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_people_metadata_id_provider_id"); + + b.ToTable("people_metadata_id"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_season_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_season_metadata_id_provider_id"); + + b.ToTable("season_metadata_id"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_show_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_show_metadata_id_provider_id"); + + b.ToTable("show_metadata_id"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_studio_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_studio_metadata_id_provider_id"); + + b.ToTable("studio_metadata_id"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -875,216 +913,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_collection_show_collections_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_collection_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_collection_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_collection_collections_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_provider_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_provider_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_show_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_show_genre_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_show_genre_genres_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_user_show_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_user_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_episode_episodes_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_episode_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_people_people_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_people_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_season_seasons_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_season_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_show_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_show_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -1143,30 +971,242 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .HasConstraintName("fk_watched_episodes_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_watched_episodes_episodes_second_id") + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_watched_episodes_episodes_episode_id") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .HasConstraintName("fk_watched_episodes_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .HasConstraintName("fk_link_user_show_users_users_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .HasConstraintName("fk_link_user_show_shows_watched_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("collection_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_collection_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_collection_metadata_id_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_episode_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_episode_metadata_id_episodes_episode_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_collection_show_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_collection_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_library_collection_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_collection_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_provider_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("provider_id") + .HasConstraintName("fk_link_library_provider_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_show_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_library_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("genre_id") + .HasConstraintName("fk_link_show_genre_genres_genre_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_show_genre_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_people_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_people_metadata_id_people_people_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_season_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_season_metadata_id_seasons_season_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_show_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_show_metadata_id_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_studio_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_studio_metadata_id_studios_studio_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); }); modelBuilder.Entity("Kyoo.Models.Collection", b => { - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); + b.Navigation("ExternalIDs"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -1176,20 +1216,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -1197,11 +1223,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -1211,16 +1232,10 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1228,14 +1243,14 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.Postgresql/Migrations/20210723224335_Triggers.cs b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs similarity index 96% rename from Kyoo.Postgresql/Migrations/20210723224335_Triggers.cs rename to Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs index a773e02b..6ee2961c 100644 --- a/Kyoo.Postgresql/Migrations/20210723224335_Triggers.cs +++ b/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs @@ -141,7 +141,7 @@ namespace Kyoo.Postgresql.Migrations // language=PostgreSQL migrationBuilder.Sql(@" CREATE VIEW library_items AS - SELECT s.id, s.slug, s.title, s.overview, s.status, s.start_air, s.end_air, s.poster, CASE + SELECT s.id, s.slug, s.title, s.overview, s.status, s.start_air, s.end_air, s.images, CASE WHEN s.is_movie THEN 'movie'::item_type ELSE 'show'::item_type END AS type @@ -149,11 +149,11 @@ namespace Kyoo.Postgresql.Migrations WHERE NOT (EXISTS ( SELECT 1 FROM link_collection_show AS l - INNER JOIN collections AS c ON l.first_id = c.id - WHERE s.id = l.second_id)) + INNER JOIN collections AS c ON l.collection_id = c.id + WHERE s.id = l.show_id)) UNION ALL SELECT -c0.id, c0.slug, c0.name AS title, c0.overview, 'unknown'::status AS status, - NULL AS start_air, NULL AS end_air, c0.poster, 'collection'::item_type AS type + NULL AS start_air, NULL AS end_air, c0.images, 'collection'::item_type AS type FROM collections AS c0"); } diff --git a/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index f2d55f24..cd338b2f 100644 --- a/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -32,6 +32,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); @@ -40,10 +44,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -75,6 +75,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("episode_number"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -104,10 +108,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("slug"); - b.Property("Thumb") - .HasColumnType("text") - .HasColumnName("thumb"); - b.Property("Title") .HasColumnType("text") .HasColumnName("title"); @@ -198,14 +198,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .HasColumnType("text") .HasColumnName("slug"); @@ -232,228 +232,6 @@ namespace Kyoo.Postgresql.Migrations b.ToView("library_items"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_collection_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_collection_show_second_id"); - - b.ToTable("link_collection_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_collection"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_collection_second_id"); - - b.ToTable("link_library_collection"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_provider"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_provider_second_id"); - - b.ToTable("link_library_provider"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_library_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_library_show_second_id"); - - b.ToTable("link_library_show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_show_genre"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_show_genre_second_id"); - - b.ToTable("link_show_genre"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_link_user_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_link_user_show_second_id"); - - b.ToTable("link_user_show"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_episode"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_episode_second_id"); - - b.ToTable("metadata_id_episode"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_people"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_people_second_id"); - - b.ToTable("metadata_id_people"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_season"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_season_second_id"); - - b.ToTable("metadata_id_season"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("integer") - .HasColumnName("first_id"); - - b.Property("SecondID") - .HasColumnType("integer") - .HasColumnName("second_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("FirstID", "SecondID") - .HasName("pk_metadata_id_show"); - - b.HasIndex("SecondID") - .HasDatabaseName("ix_metadata_id_show_second_id"); - - b.ToTable("metadata_id_show"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") @@ -462,14 +240,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Name") .HasColumnType("text") .HasColumnName("name"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -493,10 +271,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ForPeople") - .HasColumnType("boolean") - .HasColumnName("for_people"); - b.Property("PeopleID") .HasColumnType("integer") .HasColumnName("people_id"); @@ -533,13 +307,9 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); - - b.Property("LogoExtension") - .HasColumnType("text") - .HasColumnName("logo_extension"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); b.Property("Name") .HasColumnType("text") @@ -572,14 +342,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp without time zone") .HasColumnName("end_date"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("SeasonNumber") .HasColumnType("integer") .HasColumnName("season_number"); @@ -627,22 +397,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text[]") .HasColumnName("aliases"); - b.Property("Backdrop") - .HasColumnType("text") - .HasColumnName("backdrop"); - b.Property("EndAir") .HasColumnType("timestamp without time zone") .HasColumnName("end_air"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("IsMovie") .HasColumnType("boolean") .HasColumnName("is_movie"); - b.Property("Logo") - .HasColumnType("text") - .HasColumnName("logo"); - b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); @@ -651,10 +417,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("path"); - b.Property("Poster") - .HasColumnType("text") - .HasColumnName("poster"); - b.Property("Slug") .IsRequired() .HasColumnType("text") @@ -676,10 +438,6 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("title"); - b.Property("TrailerUrl") - .HasColumnType("text") - .HasColumnName("trailer_url"); - b.HasKey("ID") .HasName("pk_shows"); @@ -803,6 +561,10 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("jsonb") .HasColumnName("extra_data"); + b.Property>("Images") + .HasColumnType("jsonb") + .HasColumnName("images"); + b.Property("Password") .HasColumnType("text") .HasColumnName("password"); @@ -832,27 +594,303 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("integer") - .HasColumnName("first_id"); + .HasColumnName("user_id"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("integer") - .HasColumnName("second_id"); + .HasColumnName("episode_id"); b.Property("WatchedPercentage") .HasColumnType("integer") .HasColumnName("watched_percentage"); - b.HasKey("FirstID", "SecondID") + b.HasKey("UserID", "EpisodeID") .HasName("pk_watched_episodes"); - b.HasIndex("SecondID") - .HasDatabaseName("ix_watched_episodes_second_id"); + b.HasIndex("EpisodeID") + .HasDatabaseName("ix_watched_episodes_episode_id"); b.ToTable("watched_episodes"); }); + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("integer") + .HasColumnName("users_id"); + + b.Property("WatchedID") + .HasColumnType("integer") + .HasColumnName("watched_id"); + + b.HasKey("UsersID", "WatchedID") + .HasName("pk_link_user_show"); + + b.HasIndex("WatchedID") + .HasDatabaseName("ix_link_user_show_watched_id"); + + b.ToTable("link_user_show"); + }); + + modelBuilder.Entity("collection_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_collection_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_collection_metadata_id_provider_id"); + + b.ToTable("collection_metadata_id"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_episode_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_episode_metadata_id_provider_id"); + + b.ToTable("episode_metadata_id"); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("collection_id", "show_id") + .HasName("pk_link_collection_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_collection_show_show_id"); + + b.ToTable("link_collection_show"); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.HasKey("collection_id", "library_id") + .HasName("pk_link_library_collection"); + + b.HasIndex("library_id") + .HasDatabaseName("ix_link_library_collection_library_id"); + + b.ToTable("link_library_collection"); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("provider_id") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.HasKey("library_id", "provider_id") + .HasName("pk_link_library_provider"); + + b.HasIndex("provider_id") + .HasDatabaseName("ix_link_library_provider_provider_id"); + + b.ToTable("link_library_provider"); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("library_id", "show_id") + .HasName("pk_link_library_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_library_show_show_id"); + + b.ToTable("link_library_show"); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.Property("genre_id") + .HasColumnType("integer") + .HasColumnName("genre_id"); + + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.HasKey("genre_id", "show_id") + .HasName("pk_link_show_genre"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_show_genre_show_id"); + + b.ToTable("link_show_genre"); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_people_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_people_metadata_id_provider_id"); + + b.ToTable("people_metadata_id"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_season_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_season_metadata_id_provider_id"); + + b.ToTable("season_metadata_id"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_show_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_show_metadata_id_provider_id"); + + b.ToTable("show_metadata_id"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.Property("ResourceID") + .HasColumnType("integer") + .HasColumnName("resource_id"); + + b.Property("ProviderID") + .HasColumnType("integer") + .HasColumnName("provider_id"); + + b.Property("DataID") + .HasColumnType("text") + .HasColumnName("data_id"); + + b.Property("Link") + .HasColumnType("text") + .HasColumnName("link"); + + b.HasKey("ResourceID", "ProviderID") + .HasName("pk_studio_metadata_id"); + + b.HasIndex("ProviderID") + .HasDatabaseName("ix_studio_metadata_id_provider_id"); + + b.ToTable("studio_metadata_id"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -873,216 +911,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_collection_show_collections_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_collection_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_collection_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_collection_collections_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_provider_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_provider_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_library_show_libraries_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_library_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_show_genre_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_show_genre_genres_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .HasConstraintName("fk_link_user_show_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_link_user_show_shows_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_episode_episodes_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_episode_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_people_people_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_people_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_season_seasons_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_season_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .HasConstraintName("fk_metadata_id_show_shows_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_metadata_id_show_providers_second_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -1141,30 +969,242 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .HasConstraintName("fk_watched_episodes_users_first_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") - .HasConstraintName("fk_watched_episodes_episodes_second_id") + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_watched_episodes_episodes_episode_id") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .HasConstraintName("fk_watched_episodes_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .HasConstraintName("fk_link_user_show_users_users_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .HasConstraintName("fk_link_user_show_shows_watched_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("collection_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_collection_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_collection_metadata_id_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("episode_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_episode_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_episode_metadata_id_episodes_episode_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_collection_show_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_collection_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .HasConstraintName("fk_link_library_collection_collections_collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_collection_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_provider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_provider_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("provider_id") + .HasConstraintName("fk_link_library_provider_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .HasConstraintName("fk_link_library_show_libraries_library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_library_show_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("genre_id") + .HasConstraintName("fk_link_show_genre_genres_genre_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .HasConstraintName("fk_link_show_genre_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("people_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_people_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_people_metadata_id_people_people_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("season_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_season_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_season_metadata_id_seasons_season_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("show_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_show_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_show_metadata_id_shows_show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("studio_metadata_id", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .HasConstraintName("fk_studio_metadata_id_providers_provider_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .HasConstraintName("fk_studio_metadata_id_studios_studio_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); }); modelBuilder.Entity("Kyoo.Models.Collection", b => { - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); + b.Navigation("ExternalIDs"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -1174,20 +1214,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -1195,11 +1221,6 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -1209,16 +1230,10 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1226,14 +1241,14 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.Postgresql/PostgresContext.cs b/Kyoo.Postgresql/PostgresContext.cs index b0e534ed..36f12a8c 100644 --- a/Kyoo.Postgresql/PostgresContext.cs +++ b/Kyoo.Postgresql/PostgresContext.cs @@ -1,6 +1,8 @@ using System; +using System.Globalization; using System.Linq.Expressions; using System.Reflection; +using EFCore.NamingConventions.Internal; using Kyoo.Models; using Microsoft.EntityFrameworkCore; using Npgsql; @@ -99,9 +101,55 @@ namespace Kyoo.Postgresql .Property(x => x.ExtraData) .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + modelBuilder.Entity() + .Property(x => x.Images) + .HasColumnType("jsonb"); + base.OnModelCreating(modelBuilder); } + /// + protected override string MetadataName() + { + SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture); + return rewriter.RewriteName(typeof(T).Name + nameof(MetadataID)); + } + + /// + protected override string LinkName() + { + SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture); + return rewriter.RewriteName("Link" + typeof(T).Name + typeof(T2).Name); + } + + /// + protected override string LinkNameFk() + { + SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture); + return rewriter.RewriteName(typeof(T).Name + "ID"); + } + /// protected override bool IsDuplicateException(Exception ex) { diff --git a/Kyoo.Postgresql/PostgresModule.cs b/Kyoo.Postgresql/PostgresModule.cs index 124df770..6b84bd93 100644 --- a/Kyoo.Postgresql/PostgresModule.cs +++ b/Kyoo.Postgresql/PostgresModule.cs @@ -66,7 +66,7 @@ namespace Kyoo.Postgresql x.UseNpgsql(_configuration.GetDatabaseConnection("postgres")); if (_environment.IsDevelopment()) x.EnableDetailedErrors().EnableSensitiveDataLogging(); - }); + }, ServiceLifetime.Transient); } /// diff --git a/Kyoo.SqLite/Migrations/20210723224542_Initial.Designer.cs b/Kyoo.SqLite/Migrations/20210801171534_Initial.Designer.cs similarity index 70% rename from Kyoo.SqLite/Migrations/20210723224542_Initial.Designer.cs rename to Kyoo.SqLite/Migrations/20210801171534_Initial.Designer.cs index eca501eb..0443a20d 100644 --- a/Kyoo.SqLite/Migrations/20210723224542_Initial.Designer.cs +++ b/Kyoo.SqLite/Migrations/20210801171534_Initial.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Kyoo.SqLite.Migrations { [DbContext(typeof(SqLiteContext))] - [Migration("20210723224542_Initial")] + [Migration("20210801171534_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,21 +18,63 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.Entity("CollectionMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("CollectionMetadataID"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("EpisodeMetadataID"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Name") .HasColumnType("TEXT"); b.Property("Overview") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -57,6 +99,9 @@ namespace Kyoo.SqLite.Migrations b.Property("EpisodeNumber") .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Overview") .HasColumnType("TEXT"); @@ -79,9 +124,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAddOrUpdate() .HasColumnType("TEXT"); - b.Property("Thumb") - .HasColumnType("TEXT"); - b.Property("Title") .HasColumnType("TEXT"); @@ -152,10 +194,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("Slug") @@ -178,190 +220,16 @@ namespace Kyoo.SqLite.Migrations b.ToView("LibraryItems"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Name") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Name") .HasColumnType("TEXT"); b.Property("Slug") @@ -382,9 +250,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ForPeople") - .HasColumnType("INTEGER"); - b.Property("PeopleID") .HasColumnType("INTEGER"); @@ -412,10 +277,7 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Logo") - .HasColumnType("TEXT"); - - b.Property("LogoExtension") + b.Property("Images") .HasColumnType("TEXT"); b.Property("Name") @@ -442,10 +304,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndDate") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("SeasonNumber") @@ -484,27 +346,21 @@ namespace Kyoo.SqLite.Migrations b.Property("Aliases") .HasColumnType("TEXT"); - b.Property("Backdrop") + b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("EndAir") + b.Property("Images") .HasColumnType("TEXT"); b.Property("IsMovie") .HasColumnType("INTEGER"); - b.Property("Logo") - .HasColumnType("TEXT"); - b.Property("Overview") .HasColumnType("TEXT"); b.Property("Path") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -521,9 +377,6 @@ namespace Kyoo.SqLite.Migrations b.Property("Title") .HasColumnType("TEXT"); - b.Property("TrailerUrl") - .HasColumnType("TEXT"); - b.HasKey("ID"); b.HasIndex("Slug") @@ -618,6 +471,9 @@ namespace Kyoo.SqLite.Migrations b.Property("ExtraData") .HasColumnType("TEXT"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Password") .HasColumnType("TEXT"); @@ -641,22 +497,230 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("INTEGER"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("INTEGER"); b.Property("WatchedPercentage") .HasColumnType("INTEGER"); - b.HasKey("FirstID", "SecondID"); + b.HasKey("UserID", "EpisodeID"); - b.HasIndex("SecondID"); + b.HasIndex("EpisodeID"); b.ToTable("WatchedEpisodes"); }); + modelBuilder.Entity("LinkCollectionShow", b => + { + b.Property("CollectionID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkCollectionShow"); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.Property("CollectionID") + .HasColumnType("INTEGER"); + + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionID", "LibraryID"); + + b.HasIndex("LibraryID"); + + b.ToTable("LinkLibraryCollection"); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.HasKey("LibraryID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("LinkLibraryProvider"); + }); + + modelBuilder.Entity("LinkLibraryShow", b => + { + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("LibraryID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkLibraryShow"); + }); + + modelBuilder.Entity("LinkShowGenre", b => + { + b.Property("GenreID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("GenreID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkShowGenre"); + }); + + modelBuilder.Entity("PeopleMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("PeopleMetadataID"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("SeasonMetadataID"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("ShowMetadataID"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("INTEGER"); + + b.Property("WatchedID") + .HasColumnType("INTEGER"); + + b.HasKey("UsersID", "WatchedID"); + + b.HasIndex("WatchedID"); + + b.ToTable("LinkUserShow"); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("StudioMetadataID"); + }); + + modelBuilder.Entity("CollectionMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -675,196 +739,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -918,28 +792,182 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") + .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("LinkCollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkShowGenre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenreID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PeopleMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); }); modelBuilder.Entity("Kyoo.Models.Collection", b => { - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); + b.Navigation("ExternalIDs"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -949,20 +977,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -970,11 +984,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -984,16 +993,10 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1001,14 +1004,14 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.SqLite/Migrations/20210723224542_Initial.cs b/Kyoo.SqLite/Migrations/20210801171534_Initial.cs similarity index 68% rename from Kyoo.SqLite/Migrations/20210723224542_Initial.cs rename to Kyoo.SqLite/Migrations/20210801171534_Initial.cs index 1fa93860..a7e62b03 100644 --- a/Kyoo.SqLite/Migrations/20210723224542_Initial.cs +++ b/Kyoo.SqLite/Migrations/20210801171534_Initial.cs @@ -15,7 +15,7 @@ namespace Kyoo.SqLite.Migrations .Annotation("Sqlite:Autoincrement", true), Slug = table.Column(type: "TEXT", nullable: false), Name = table.Column(type: "TEXT", nullable: true), - Poster = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true), Overview = table.Column(type: "TEXT", nullable: true) }, constraints: table => @@ -60,7 +60,7 @@ namespace Kyoo.SqLite.Migrations .Annotation("Sqlite:Autoincrement", true), Slug = table.Column(type: "TEXT", nullable: false), Name = table.Column(type: "TEXT", nullable: true), - Poster = table.Column(type: "TEXT", nullable: true) + Images = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -75,8 +75,7 @@ namespace Kyoo.SqLite.Migrations .Annotation("Sqlite:Autoincrement", true), Slug = table.Column(type: "TEXT", nullable: false), Name = table.Column(type: "TEXT", nullable: true), - Logo = table.Column(type: "TEXT", nullable: true), - LogoExtension = table.Column(type: "TEXT", nullable: true) + Images = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -108,7 +107,8 @@ namespace Kyoo.SqLite.Migrations Email = table.Column(type: "TEXT", nullable: true), Password = table.Column(type: "TEXT", nullable: true), Permissions = table.Column(type: "TEXT", nullable: true), - ExtraData = table.Column(type: "TEXT", nullable: true) + ExtraData = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -116,74 +116,100 @@ namespace Kyoo.SqLite.Migrations }); migrationBuilder.CreateTable( - name: "Link", + name: "LinkLibraryCollection", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) + CollectionID = table.Column(type: "INTEGER", nullable: false), + LibraryID = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_LinkLibraryCollection", x => new { x.CollectionID, x.LibraryID }); table.ForeignKey( - name: "FK_Link_Collections_SecondID", - column: x => x.SecondID, + name: "FK_LinkLibraryCollection_Collections_CollectionID", + column: x => x.CollectionID, principalTable: "Collections", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_Link_Libraries_FirstID", - column: x => x.FirstID, + name: "FK_LinkLibraryCollection_Libraries_LibraryID", + column: x => x.LibraryID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "Link", + name: "CollectionMetadataID", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), + DataID = table.Column(type: "TEXT", nullable: true), + Link = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_CollectionMetadataID", x => new { x.ResourceID, x.ProviderID }); table.ForeignKey( - name: "FK_Link_Libraries_FirstID", - column: x => x.FirstID, - principalTable: "Libraries", + name: "FK_CollectionMetadataID_Collections_ResourceID", + column: x => x.ResourceID, + principalTable: "Collections", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_Link_Providers_SecondID", - column: x => x.SecondID, + name: "FK_CollectionMetadataID_Providers_ProviderID", + column: x => x.ProviderID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "MetadataID", + name: "LinkLibraryProvider", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), + LibraryID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LinkLibraryProvider", x => new { x.LibraryID, x.ProviderID }); + table.ForeignKey( + name: "FK_LinkLibraryProvider_Libraries_LibraryID", + column: x => x.LibraryID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LinkLibraryProvider_Providers_ProviderID", + column: x => x.ProviderID, + principalTable: "Providers", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PeopleMetadataID", + columns: table => new + { + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), DataID = table.Column(type: "TEXT", nullable: true), Link = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_MetadataID", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_PeopleMetadataID", x => new { x.ResourceID, x.ProviderID }); table.ForeignKey( - name: "FK_MetadataID_People_FirstID", - column: x => x.FirstID, + name: "FK_PeopleMetadataID_People_ResourceID", + column: x => x.ResourceID, principalTable: "People", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_MetadataID_Providers_SecondID", - column: x => x.SecondID, + name: "FK_PeopleMetadataID_Providers_ProviderID", + column: x => x.ProviderID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -201,12 +227,9 @@ namespace Kyoo.SqLite.Migrations Path = table.Column(type: "TEXT", nullable: true), Overview = table.Column(type: "TEXT", nullable: true), Status = table.Column(type: "INTEGER", nullable: false), - TrailerUrl = table.Column(type: "TEXT", nullable: true), StartAir = table.Column(type: "TEXT", nullable: true), EndAir = table.Column(type: "TEXT", nullable: true), - Poster = table.Column(type: "TEXT", nullable: true), - Logo = table.Column(type: "TEXT", nullable: true), - Backdrop = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true), IsMovie = table.Column(type: "INTEGER", nullable: false), StudioID = table.Column(type: "INTEGER", nullable: true) }, @@ -222,134 +245,133 @@ namespace Kyoo.SqLite.Migrations }); migrationBuilder.CreateTable( - name: "Link", + name: "StudioMetadataID", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_Link_Collections_FirstID", - column: x => x.FirstID, - principalTable: "Collections", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Link_Shows_SecondID", - column: x => x.SecondID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Link", - columns: table => new - { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_Link_Libraries_FirstID", - column: x => x.FirstID, - principalTable: "Libraries", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Link_Shows_SecondID", - column: x => x.SecondID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Link", - columns: table => new - { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_Link_Genres_SecondID", - column: x => x.SecondID, - principalTable: "Genres", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Link_Shows_FirstID", - column: x => x.FirstID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Link", - columns: table => new - { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_Link_Shows_SecondID", - column: x => x.SecondID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Link_Users_FirstID", - column: x => x.FirstID, - principalTable: "Users", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "MetadataID", - columns: table => new - { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), DataID = table.Column(type: "TEXT", nullable: true), Link = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_MetadataID", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_StudioMetadataID", x => new { x.ResourceID, x.ProviderID }); table.ForeignKey( - name: "FK_MetadataID_Providers_SecondID", - column: x => x.SecondID, + name: "FK_StudioMetadataID_Providers_ProviderID", + column: x => x.ProviderID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_MetadataID_Shows_FirstID", - column: x => x.FirstID, + name: "FK_StudioMetadataID_Studios_ResourceID", + column: x => x.ResourceID, + principalTable: "Studios", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LinkCollectionShow", + columns: table => new + { + CollectionID = table.Column(type: "INTEGER", nullable: false), + ShowID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LinkCollectionShow", x => new { x.CollectionID, x.ShowID }); + table.ForeignKey( + name: "FK_LinkCollectionShow_Collections_CollectionID", + column: x => x.CollectionID, + principalTable: "Collections", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LinkCollectionShow_Shows_ShowID", + column: x => x.ShowID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "LinkLibraryShow", + columns: table => new + { + LibraryID = table.Column(type: "INTEGER", nullable: false), + ShowID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LinkLibraryShow", x => new { x.LibraryID, x.ShowID }); + table.ForeignKey( + name: "FK_LinkLibraryShow_Libraries_LibraryID", + column: x => x.LibraryID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LinkLibraryShow_Shows_ShowID", + column: x => x.ShowID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LinkShowGenre", + columns: table => new + { + GenreID = table.Column(type: "INTEGER", nullable: false), + ShowID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LinkShowGenre", x => new { x.GenreID, x.ShowID }); + table.ForeignKey( + name: "FK_LinkShowGenre_Genres_GenreID", + column: x => x.GenreID, + principalTable: "Genres", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LinkShowGenre_Shows_ShowID", + column: x => x.ShowID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LinkUserShow", + columns: table => new + { + UsersID = table.Column(type: "INTEGER", nullable: false), + WatchedID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LinkUserShow", x => new { x.UsersID, x.WatchedID }); + table.ForeignKey( + name: "FK_LinkUserShow_Shows_WatchedID", + column: x => x.WatchedID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LinkUserShow_Users_UsersID", + column: x => x.UsersID, + principalTable: "Users", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "PeopleRoles", columns: table => new { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - ForPeople = table.Column(type: "INTEGER", nullable: false), PeopleID = table.Column(type: "INTEGER", nullable: false), ShowID = table.Column(type: "INTEGER", nullable: false), Type = table.Column(type: "TEXT", nullable: true), @@ -385,7 +407,7 @@ namespace Kyoo.SqLite.Migrations Overview = table.Column(type: "TEXT", nullable: true), StartDate = table.Column(type: "TEXT", nullable: true), EndDate = table.Column(type: "TEXT", nullable: true), - Poster = table.Column(type: "TEXT", nullable: true) + Images = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -398,6 +420,32 @@ namespace Kyoo.SqLite.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "ShowMetadataID", + columns: table => new + { + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), + DataID = table.Column(type: "TEXT", nullable: true), + Link = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ShowMetadataID", x => new { x.ResourceID, x.ProviderID }); + table.ForeignKey( + name: "FK_ShowMetadataID_Providers_ProviderID", + column: x => x.ProviderID, + principalTable: "Providers", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ShowMetadataID_Shows_ResourceID", + column: x => x.ResourceID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "Episodes", columns: table => new @@ -411,7 +459,7 @@ namespace Kyoo.SqLite.Migrations EpisodeNumber = table.Column(type: "INTEGER", nullable: true), AbsoluteNumber = table.Column(type: "INTEGER", nullable: true), Path = table.Column(type: "TEXT", nullable: true), - Thumb = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true), Title = table.Column(type: "TEXT", nullable: true), Overview = table.Column(type: "TEXT", nullable: true), ReleaseDate = table.Column(type: "TEXT", nullable: true) @@ -434,52 +482,52 @@ namespace Kyoo.SqLite.Migrations }); migrationBuilder.CreateTable( - name: "MetadataID", + name: "SeasonMetadataID", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), DataID = table.Column(type: "TEXT", nullable: true), Link = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_MetadataID", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_SeasonMetadataID", x => new { x.ResourceID, x.ProviderID }); table.ForeignKey( - name: "FK_MetadataID_Providers_SecondID", - column: x => x.SecondID, + name: "FK_SeasonMetadataID_Providers_ProviderID", + column: x => x.ProviderID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_MetadataID_Seasons_FirstID", - column: x => x.FirstID, + name: "FK_SeasonMetadataID_Seasons_ResourceID", + column: x => x.ResourceID, principalTable: "Seasons", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "MetadataID", + name: "EpisodeMetadataID", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), + ResourceID = table.Column(type: "INTEGER", nullable: false), + ProviderID = table.Column(type: "INTEGER", nullable: false), DataID = table.Column(type: "TEXT", nullable: true), Link = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_MetadataID", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_EpisodeMetadataID", x => new { x.ResourceID, x.ProviderID }); table.ForeignKey( - name: "FK_MetadataID_Episodes_FirstID", - column: x => x.FirstID, + name: "FK_EpisodeMetadataID_Episodes_ResourceID", + column: x => x.ResourceID, principalTable: "Episodes", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_MetadataID_Providers_SecondID", - column: x => x.SecondID, + name: "FK_EpisodeMetadataID_Providers_ProviderID", + column: x => x.ProviderID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -518,33 +566,43 @@ namespace Kyoo.SqLite.Migrations name: "WatchedEpisodes", columns: table => new { - FirstID = table.Column(type: "INTEGER", nullable: false), - SecondID = table.Column(type: "INTEGER", nullable: false), + UserID = table.Column(type: "INTEGER", nullable: false), + EpisodeID = table.Column(type: "INTEGER", nullable: false), WatchedPercentage = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_WatchedEpisodes", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_WatchedEpisodes", x => new { x.UserID, x.EpisodeID }); table.ForeignKey( - name: "FK_WatchedEpisodes_Episodes_SecondID", - column: x => x.SecondID, + name: "FK_WatchedEpisodes_Episodes_EpisodeID", + column: x => x.EpisodeID, principalTable: "Episodes", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_WatchedEpisodes_Users_FirstID", - column: x => x.FirstID, + name: "FK_WatchedEpisodes_Users_UserID", + column: x => x.UserID, principalTable: "Users", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateIndex( + name: "IX_CollectionMetadataID_ProviderID", + table: "CollectionMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Collections_Slug", table: "Collections", column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_EpisodeMetadataID_ProviderID", + table: "EpisodeMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Episodes_SeasonID", table: "Episodes", @@ -575,54 +633,34 @@ namespace Kyoo.SqLite.Migrations unique: true); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkCollectionShow_ShowID", + table: "LinkCollectionShow", + column: "ShowID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkLibraryCollection_LibraryID", + table: "LinkLibraryCollection", + column: "LibraryID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkLibraryProvider_ProviderID", + table: "LinkLibraryProvider", + column: "ProviderID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkLibraryShow_ShowID", + table: "LinkLibraryShow", + column: "ShowID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); + name: "IX_LinkShowGenre_ShowID", + table: "LinkShowGenre", + column: "ShowID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataID_SecondID", - table: "MetadataID", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataID_SecondID", - table: "MetadataID", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataID_SecondID", - table: "MetadataID", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataID_SecondID", - table: "MetadataID", - column: "SecondID"); + name: "IX_LinkUserShow_WatchedID", + table: "LinkUserShow", + column: "WatchedID"); migrationBuilder.CreateIndex( name: "IX_People_Slug", @@ -630,6 +668,11 @@ namespace Kyoo.SqLite.Migrations column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_PeopleMetadataID_ProviderID", + table: "PeopleMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_PeopleRoles_PeopleID", table: "PeopleRoles", @@ -646,6 +689,11 @@ namespace Kyoo.SqLite.Migrations column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_SeasonMetadataID_ProviderID", + table: "SeasonMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Seasons_ShowID_SeasonNumber", table: "Seasons", @@ -658,6 +706,11 @@ namespace Kyoo.SqLite.Migrations column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_ShowMetadataID_ProviderID", + table: "ShowMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Shows_Slug", table: "Shows", @@ -669,6 +722,11 @@ namespace Kyoo.SqLite.Migrations table: "Shows", column: "StudioID"); + migrationBuilder.CreateIndex( + name: "IX_StudioMetadataID_ProviderID", + table: "StudioMetadataID", + column: "ProviderID"); + migrationBuilder.CreateIndex( name: "IX_Studios_Slug", table: "Studios", @@ -694,46 +752,52 @@ namespace Kyoo.SqLite.Migrations unique: true); migrationBuilder.CreateIndex( - name: "IX_WatchedEpisodes_SecondID", + name: "IX_WatchedEpisodes_EpisodeID", table: "WatchedEpisodes", - column: "SecondID"); + column: "EpisodeID"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "Link"); + name: "CollectionMetadataID"); migrationBuilder.DropTable( - name: "Link"); + name: "EpisodeMetadataID"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkCollectionShow"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkLibraryCollection"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkLibraryProvider"); migrationBuilder.DropTable( - name: "Link"); + name: "LinkLibraryShow"); migrationBuilder.DropTable( - name: "MetadataID"); + name: "LinkShowGenre"); migrationBuilder.DropTable( - name: "MetadataID"); + name: "LinkUserShow"); migrationBuilder.DropTable( - name: "MetadataID"); - - migrationBuilder.DropTable( - name: "MetadataID"); + name: "PeopleMetadataID"); migrationBuilder.DropTable( name: "PeopleRoles"); + migrationBuilder.DropTable( + name: "SeasonMetadataID"); + + migrationBuilder.DropTable( + name: "ShowMetadataID"); + + migrationBuilder.DropTable( + name: "StudioMetadataID"); + migrationBuilder.DropTable( name: "Tracks"); @@ -750,10 +814,10 @@ namespace Kyoo.SqLite.Migrations name: "Genres"); migrationBuilder.DropTable( - name: "Providers"); + name: "People"); migrationBuilder.DropTable( - name: "People"); + name: "Providers"); migrationBuilder.DropTable( name: "Episodes"); diff --git a/Kyoo.SqLite/Migrations/20210723224550_Triggers.Designer.cs b/Kyoo.SqLite/Migrations/20210801171544_Triggers.Designer.cs similarity index 70% rename from Kyoo.SqLite/Migrations/20210723224550_Triggers.Designer.cs rename to Kyoo.SqLite/Migrations/20210801171544_Triggers.Designer.cs index 059a3aa4..05c1c174 100644 --- a/Kyoo.SqLite/Migrations/20210723224550_Triggers.Designer.cs +++ b/Kyoo.SqLite/Migrations/20210801171544_Triggers.Designer.cs @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Kyoo.SqLite.Migrations { [DbContext(typeof(SqLiteContext))] - [Migration("20210723224550_Triggers")] + [Migration("20210801171544_Triggers")] partial class Triggers { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -18,21 +18,63 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.Entity("CollectionMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("CollectionMetadataID"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("EpisodeMetadataID"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Name") .HasColumnType("TEXT"); b.Property("Overview") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -57,6 +99,9 @@ namespace Kyoo.SqLite.Migrations b.Property("EpisodeNumber") .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Overview") .HasColumnType("TEXT"); @@ -79,9 +124,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAddOrUpdate() .HasColumnType("TEXT"); - b.Property("Thumb") - .HasColumnType("TEXT"); - b.Property("Title") .HasColumnType("TEXT"); @@ -152,10 +194,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("Slug") @@ -178,190 +220,16 @@ namespace Kyoo.SqLite.Migrations b.ToView("LibraryItems"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Name") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Name") .HasColumnType("TEXT"); b.Property("Slug") @@ -382,9 +250,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ForPeople") - .HasColumnType("INTEGER"); - b.Property("PeopleID") .HasColumnType("INTEGER"); @@ -412,10 +277,7 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Logo") - .HasColumnType("TEXT"); - - b.Property("LogoExtension") + b.Property("Images") .HasColumnType("TEXT"); b.Property("Name") @@ -442,10 +304,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndDate") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("SeasonNumber") @@ -484,27 +346,21 @@ namespace Kyoo.SqLite.Migrations b.Property("Aliases") .HasColumnType("TEXT"); - b.Property("Backdrop") + b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("EndAir") + b.Property("Images") .HasColumnType("TEXT"); b.Property("IsMovie") .HasColumnType("INTEGER"); - b.Property("Logo") - .HasColumnType("TEXT"); - b.Property("Overview") .HasColumnType("TEXT"); b.Property("Path") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -521,9 +377,6 @@ namespace Kyoo.SqLite.Migrations b.Property("Title") .HasColumnType("TEXT"); - b.Property("TrailerUrl") - .HasColumnType("TEXT"); - b.HasKey("ID"); b.HasIndex("Slug") @@ -618,6 +471,9 @@ namespace Kyoo.SqLite.Migrations b.Property("ExtraData") .HasColumnType("TEXT"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Password") .HasColumnType("TEXT"); @@ -641,22 +497,230 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("INTEGER"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("INTEGER"); b.Property("WatchedPercentage") .HasColumnType("INTEGER"); - b.HasKey("FirstID", "SecondID"); + b.HasKey("UserID", "EpisodeID"); - b.HasIndex("SecondID"); + b.HasIndex("EpisodeID"); b.ToTable("WatchedEpisodes"); }); + modelBuilder.Entity("LinkCollectionShow", b => + { + b.Property("CollectionID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkCollectionShow"); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.Property("CollectionID") + .HasColumnType("INTEGER"); + + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionID", "LibraryID"); + + b.HasIndex("LibraryID"); + + b.ToTable("LinkLibraryCollection"); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.HasKey("LibraryID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("LinkLibraryProvider"); + }); + + modelBuilder.Entity("LinkLibraryShow", b => + { + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("LibraryID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkLibraryShow"); + }); + + modelBuilder.Entity("LinkShowGenre", b => + { + b.Property("GenreID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("GenreID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkShowGenre"); + }); + + modelBuilder.Entity("PeopleMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("PeopleMetadataID"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("SeasonMetadataID"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("ShowMetadataID"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("INTEGER"); + + b.Property("WatchedID") + .HasColumnType("INTEGER"); + + b.HasKey("UsersID", "WatchedID"); + + b.HasIndex("WatchedID"); + + b.ToTable("LinkUserShow"); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("StudioMetadataID"); + }); + + modelBuilder.Entity("CollectionMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -675,196 +739,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -918,28 +792,182 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") + .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("LinkCollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkShowGenre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenreID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PeopleMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); }); modelBuilder.Entity("Kyoo.Models.Collection", b => { - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); + b.Navigation("ExternalIDs"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -949,20 +977,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -970,11 +984,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -984,16 +993,10 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -1001,14 +1004,14 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.SqLite/Migrations/20210723224550_Triggers.cs b/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs similarity index 95% rename from Kyoo.SqLite/Migrations/20210723224550_Triggers.cs rename to Kyoo.SqLite/Migrations/20210801171544_Triggers.cs index 370fdd37..789bc182 100644 --- a/Kyoo.SqLite/Migrations/20210723224550_Triggers.cs +++ b/Kyoo.SqLite/Migrations/20210801171544_Triggers.cs @@ -154,19 +154,19 @@ namespace Kyoo.SqLite.Migrations // language=SQLite migrationBuilder.Sql(@" CREATE VIEW LibraryItems AS - SELECT s.ID, s.Slug, s.Title, s.Overview, s.Status, s.StartAir, s.EndAir, s.Poster, CASE + SELECT s.ID, s.Slug, s.Title, s.Overview, s.Status, s.StartAir, s.EndAir, s.Images, CASE WHEN s.IsMovie THEN 1 ELSE 0 END AS Type FROM Shows AS s WHERE NOT (EXISTS ( SELECT 1 - FROM 'Link' AS l - INNER JOIN Collections AS c ON l.FirstID = c.ID - WHERE s.ID = l.SecondID)) + FROM LinkCollectionShow AS l + INNER JOIN Collections AS c ON l.CollectionID = c.ID + WHERE s.ID = l.ShowID)) UNION ALL - SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 3 AS Status, - NULL AS StartAir, NULL AS EndAir, c0.Poster, 2 AS Type + SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 0 AS Status, + NULL AS StartAir, NULL AS EndAir, c0.Images, 2 AS Type FROM collections AS c0"); } diff --git a/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs b/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs index 12f5d94b..50c96524 100644 --- a/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs +++ b/Kyoo.SqLite/Migrations/SqLiteContextModelSnapshot.cs @@ -16,21 +16,63 @@ namespace Kyoo.SqLite.Migrations modelBuilder .HasAnnotation("ProductVersion", "5.0.8"); + modelBuilder.Entity("CollectionMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("CollectionMetadataID"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("EpisodeMetadataID"); + }); + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Name") .HasColumnType("TEXT"); b.Property("Overview") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -55,6 +97,9 @@ namespace Kyoo.SqLite.Migrations b.Property("EpisodeNumber") .HasColumnType("INTEGER"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Overview") .HasColumnType("TEXT"); @@ -77,9 +122,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAddOrUpdate() .HasColumnType("TEXT"); - b.Property("Thumb") - .HasColumnType("TEXT"); - b.Property("Title") .HasColumnType("TEXT"); @@ -150,10 +192,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("Slug") @@ -176,190 +218,16 @@ namespace Kyoo.SqLite.Migrations b.ToView("LibraryItems"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("FirstID") - .HasColumnType("INTEGER"); - - b.Property("SecondID") - .HasColumnType("INTEGER"); - - b.Property("DataID") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("MetadataID"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Property("ID") .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Name") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Name") .HasColumnType("TEXT"); b.Property("Slug") @@ -380,9 +248,6 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ForPeople") - .HasColumnType("INTEGER"); - b.Property("PeopleID") .HasColumnType("INTEGER"); @@ -410,10 +275,7 @@ namespace Kyoo.SqLite.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Logo") - .HasColumnType("TEXT"); - - b.Property("LogoExtension") + b.Property("Images") .HasColumnType("TEXT"); b.Property("Name") @@ -440,10 +302,10 @@ namespace Kyoo.SqLite.Migrations b.Property("EndDate") .HasColumnType("TEXT"); - b.Property("Overview") + b.Property("Images") .HasColumnType("TEXT"); - b.Property("Poster") + b.Property("Overview") .HasColumnType("TEXT"); b.Property("SeasonNumber") @@ -482,27 +344,21 @@ namespace Kyoo.SqLite.Migrations b.Property("Aliases") .HasColumnType("TEXT"); - b.Property("Backdrop") + b.Property("EndAir") .HasColumnType("TEXT"); - b.Property("EndAir") + b.Property("Images") .HasColumnType("TEXT"); b.Property("IsMovie") .HasColumnType("INTEGER"); - b.Property("Logo") - .HasColumnType("TEXT"); - b.Property("Overview") .HasColumnType("TEXT"); b.Property("Path") .HasColumnType("TEXT"); - b.Property("Poster") - .HasColumnType("TEXT"); - b.Property("Slug") .IsRequired() .HasColumnType("TEXT"); @@ -519,9 +375,6 @@ namespace Kyoo.SqLite.Migrations b.Property("Title") .HasColumnType("TEXT"); - b.Property("TrailerUrl") - .HasColumnType("TEXT"); - b.HasKey("ID"); b.HasIndex("Slug") @@ -616,6 +469,9 @@ namespace Kyoo.SqLite.Migrations b.Property("ExtraData") .HasColumnType("TEXT"); + b.Property("Images") + .HasColumnType("TEXT"); + b.Property("Password") .HasColumnType("TEXT"); @@ -639,22 +495,230 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.Property("FirstID") + b.Property("UserID") .HasColumnType("INTEGER"); - b.Property("SecondID") + b.Property("EpisodeID") .HasColumnType("INTEGER"); b.Property("WatchedPercentage") .HasColumnType("INTEGER"); - b.HasKey("FirstID", "SecondID"); + b.HasKey("UserID", "EpisodeID"); - b.HasIndex("SecondID"); + b.HasIndex("EpisodeID"); b.ToTable("WatchedEpisodes"); }); + modelBuilder.Entity("LinkCollectionShow", b => + { + b.Property("CollectionID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkCollectionShow"); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.Property("CollectionID") + .HasColumnType("INTEGER"); + + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionID", "LibraryID"); + + b.HasIndex("LibraryID"); + + b.ToTable("LinkLibraryCollection"); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.HasKey("LibraryID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("LinkLibraryProvider"); + }); + + modelBuilder.Entity("LinkLibraryShow", b => + { + b.Property("LibraryID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("LibraryID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkLibraryShow"); + }); + + modelBuilder.Entity("LinkShowGenre", b => + { + b.Property("GenreID") + .HasColumnType("INTEGER"); + + b.Property("ShowID") + .HasColumnType("INTEGER"); + + b.HasKey("GenreID", "ShowID"); + + b.HasIndex("ShowID"); + + b.ToTable("LinkShowGenre"); + }); + + modelBuilder.Entity("PeopleMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("PeopleMetadataID"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("SeasonMetadataID"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("ShowMetadataID"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("INTEGER"); + + b.Property("WatchedID") + .HasColumnType("INTEGER"); + + b.HasKey("UsersID", "WatchedID"); + + b.HasIndex("WatchedID"); + + b.ToTable("LinkUserShow"); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.Property("ResourceID") + .HasColumnType("INTEGER"); + + b.Property("ProviderID") + .HasColumnType("INTEGER"); + + b.Property("DataID") + .HasColumnType("TEXT"); + + b.Property("Link") + .HasColumnType("TEXT"); + + b.HasKey("ResourceID", "ProviderID"); + + b.HasIndex("ProviderID"); + + b.ToTable("StudioMetadataID"); + }); + + modelBuilder.Entity("CollectionMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("EpisodeMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Episode", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + modelBuilder.Entity("Kyoo.Models.Episode", b => { b.HasOne("Kyoo.Models.Season", "Season") @@ -673,196 +737,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.People", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Season", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("ExternalIDs") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Provider", "Second") - .WithMany() - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => { b.HasOne("Kyoo.Models.People", "People") @@ -916,28 +790,182 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b => { - b.HasOne("Kyoo.Models.User", "First") - .WithMany("CurrentlyWatching") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Episode", "Second") + b.HasOne("Kyoo.Models.Episode", "Episode") .WithMany() - .HasForeignKey("SecondID") + .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("First"); + b.HasOne("Kyoo.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Second"); + b.Navigation("Episode"); + }); + + modelBuilder.Entity("LinkCollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryCollection", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryProvider", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Provider", null) + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkLibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibraryID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LinkShowGenre", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenreID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PeopleMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.People", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("SeasonMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("ShowMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("StudioMetadataID", b => + { + b.HasOne("Kyoo.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Studio", null) + .WithMany("ExternalIDs") + .HasForeignKey("ResourceID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); }); modelBuilder.Entity("Kyoo.Models.Collection", b => { - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); + b.Navigation("ExternalIDs"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -947,20 +975,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -968,11 +982,6 @@ namespace Kyoo.SqLite.Migrations b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.Provider", b => - { - b.Navigation("LibraryLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Navigation("Episodes"); @@ -982,16 +991,10 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); @@ -999,14 +1002,14 @@ namespace Kyoo.SqLite.Migrations modelBuilder.Entity("Kyoo.Models.Studio", b => { + b.Navigation("ExternalIDs"); + b.Navigation("Shows"); }); modelBuilder.Entity("Kyoo.Models.User", b => { b.Navigation("CurrentlyWatching"); - - b.Navigation("ShowLinks"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo.SqLite/SqLiteContext.cs b/Kyoo.SqLite/SqLiteContext.cs index 23145cf1..cbd44659 100644 --- a/Kyoo.SqLite/SqLiteContext.cs +++ b/Kyoo.SqLite/SqLiteContext.cs @@ -102,12 +102,41 @@ namespace Kyoo.SqLite .Property(x => x.Type) .HasConversion(); - ValueConverter, string> jsonConvertor = new( + ValueConverter, string> extraDataConvertor = new( x => JsonConvert.SerializeObject(x), x => JsonConvert.DeserializeObject>(x)); modelBuilder.Entity() .Property(x => x.ExtraData) + .HasConversion(extraDataConvertor); + + ValueConverter, string> jsonConvertor = new( + x => JsonConvert.SerializeObject(x), + x => JsonConvert.DeserializeObject>(x)); + modelBuilder.Entity() + .Property(x => x.Images) .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() + .Property(x => x.Images) + .HasConversion(jsonConvertor); + modelBuilder.Entity() .ToView("LibraryItems") @@ -115,6 +144,24 @@ namespace Kyoo.SqLite base.OnModelCreating(modelBuilder); } + /// + protected override string MetadataName() + { + return typeof(T).Name + nameof(MetadataID); + } + + /// + protected override string LinkName() + { + return "Link" + typeof(T).Name + typeof(T2).Name; + } + + /// + protected override string LinkNameFk() + { + return typeof(T).Name + "ID"; + } + /// protected override bool IsDuplicateException(Exception ex) { diff --git a/Kyoo.SqLite/SqLiteModule.cs b/Kyoo.SqLite/SqLiteModule.cs index 34802b20..96c29836 100644 --- a/Kyoo.SqLite/SqLiteModule.cs +++ b/Kyoo.SqLite/SqLiteModule.cs @@ -66,7 +66,7 @@ namespace Kyoo.SqLite x.UseSqlite(_configuration.GetDatabaseConnection("sqlite")); if (_environment.IsDevelopment()) x.EnableDetailedErrors().EnableSensitiveDataLogging(); - }); + }, ServiceLifetime.Transient); } /// diff --git a/Kyoo.Tests/Database/RepositoryActivator.cs b/Kyoo.Tests/Database/RepositoryActivator.cs deleted file mode 100644 index ee6aa4df..00000000 --- a/Kyoo.Tests/Database/RepositoryActivator.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Threading.Tasks; -using Kyoo.Controllers; -using Xunit.Abstractions; - -namespace Kyoo.Tests -{ - public class RepositoryActivator : IDisposable, IAsyncDisposable - { - public TestContext Context { get; } - public ILibraryManager LibraryManager { get; } - - - private readonly DatabaseContext _database; - - public RepositoryActivator(ITestOutputHelper output, PostgresFixture postgres = null) - { - Context = postgres == null - ? new SqLiteTestContext(output) - : new PostgresTestContext(postgres, output); - _database = Context.New(); - - ProviderRepository provider = new(_database); - LibraryRepository library = new(_database, provider); - CollectionRepository collection = new(_database); - GenreRepository genre = new(_database); - StudioRepository studio = new(_database); - PeopleRepository people = new(_database, provider, - new Lazy(() => LibraryManager.ShowRepository)); - ShowRepository show = new(_database, studio, people, genre, provider); - SeasonRepository season = new(_database, provider); - LibraryItemRepository libraryItem = new(_database, - new Lazy(() => LibraryManager.LibraryRepository)); - TrackRepository track = new(_database); - EpisodeRepository episode = new(_database, provider, track); - UserRepository user = new(_database); - - LibraryManager = new LibraryManager(new IBaseRepository[] { - provider, - library, - libraryItem, - collection, - show, - season, - episode, - track, - people, - studio, - genre, - user - }); - } - - public void Dispose() - { - _database.Dispose(); - Context.Dispose(); - GC.SuppressFinalize(this); - } - - public async ValueTask DisposeAsync() - { - await _database.DisposeAsync(); - await Context.DisposeAsync(); - } - } -} \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs deleted file mode 100644 index 73691bf7..00000000 --- a/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Kyoo.Controllers; -using Kyoo.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace SqLite - { - public class CollectionTests : ACollectionTests - { - public CollectionTests(ITestOutputHelper output) - : base(new RepositoryActivator(output)) { } - } - } - - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class CollectionTests : ACollectionTests - { - public CollectionTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class ACollectionTests : RepositoryTests - { - private readonly ICollectionRepository _repository; - - protected ACollectionTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.CollectionRepository; - } - } -} \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs b/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs deleted file mode 100644 index 079f50cf..00000000 --- a/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using Kyoo.Controllers; -using Kyoo.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace SqLite - { - public class LibraryTests : ALibraryTests - { - public LibraryTests(ITestOutputHelper output) - : base(new RepositoryActivator(output)) { } - } - } - - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class LibraryTests : ALibraryTests - { - public LibraryTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class ALibraryTests : RepositoryTests - { - private readonly ILibraryRepository _repository; - - protected ALibraryTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.LibraryRepository; - } - - [Fact] - public async Task CreateWithProvider() - { - Library library = TestSample.GetNew(); - library.Providers = new[] { TestSample.Get() }; - await _repository.Create(library); - Library retrieved = await _repository.Get(2); - await Repositories.LibraryManager.Load(retrieved, x => x.Providers); - Assert.Equal(1, retrieved.Providers.Count); - Assert.Equal(TestSample.Get().Slug, retrieved.Providers.First().Slug); - } - } -} \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs b/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs deleted file mode 100644 index 23d40bfe..00000000 --- a/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Kyoo.Controllers; -using Kyoo.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace SqLite - { - public class PeopleTests : APeopleTests - { - public PeopleTests(ITestOutputHelper output) - : base(new RepositoryActivator(output)) { } - } - } - - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class PeopleTests : APeopleTests - { - public PeopleTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class APeopleTests : RepositoryTests - { - private readonly IPeopleRepository _repository; - - protected APeopleTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.PeopleRepository; - } - } -} \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs deleted file mode 100644 index b1692747..00000000 --- a/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Threading.Tasks; -using Kyoo.Controllers; -using Kyoo.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace SqLite - { - public class SeasonTests : ASeasonTests - { - public SeasonTests(ITestOutputHelper output) - : base(new RepositoryActivator(output)) { } - } - } - - - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class SeasonTests : ASeasonTests - { - public SeasonTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class ASeasonTests : RepositoryTests - { - private readonly ISeasonRepository _repository; - - protected ASeasonTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.SeasonRepository; - } - - [Fact] - public async Task SlugEditTest() - { - Season season = await _repository.Get(1); - Assert.Equal("anohana-s1", season.Slug); - Show show = new() - { - ID = season.ShowID, - Slug = "new-slug" - }; - await Repositories.LibraryManager.ShowRepository.Edit(show, false); - season = await _repository.Get(1); - Assert.Equal("new-slug-s1", season.Slug); - } - - [Fact] - public async Task SeasonNumberEditTest() - { - Season season = await _repository.Get(1); - Assert.Equal("anohana-s1", season.Slug); - await _repository.Edit(new Season - { - ID = 1, - SeasonNumber = 2 - }, false); - season = await _repository.Get(1); - Assert.Equal("anohana-s2", season.Slug); - } - - [Fact] - public async Task SeasonCreationSlugTest() - { - Season season = await _repository.Create(new Season - { - ShowID = TestSample.Get().ID, - SeasonNumber = 2 - }); - Assert.Equal($"{TestSample.Get().Slug}-s2", season.Slug); - } - } -} \ No newline at end of file diff --git a/Kyoo.Tests/Utility/MergerTests.cs b/Kyoo.Tests/Utility/MergerTests.cs deleted file mode 100644 index 285532c2..00000000 --- a/Kyoo.Tests/Utility/MergerTests.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Kyoo.Models; -using Kyoo.Models.Attributes; -using Xunit; - -namespace Kyoo.Tests.Utility -{ - public class MergerTests - { - [Fact] - public void NullifyTest() - { - Genre genre = new("test") - { - ID = 5 - }; - Merger.Nullify(genre); - Assert.Equal(0, genre.ID); - Assert.Null(genre.Name); - Assert.Null(genre.Slug); - } - - [Fact] - public void MergeTest() - { - Genre genre = new() - { - ID = 5 - }; - Genre genre2 = new() - { - Name = "test" - }; - Genre ret = Merger.Merge(genre, genre2); - Assert.True(ReferenceEquals(genre, ret)); - Assert.Equal(5, ret.ID); - Assert.Equal("test", genre.Name); - Assert.Null(genre.Slug); - } - - [Fact] - [SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")] - public void MergeNullTests() - { - Genre genre = new() - { - ID = 5 - }; - Assert.True(ReferenceEquals(genre, Merger.Merge(genre, null))); - Assert.True(ReferenceEquals(genre, Merger.Merge(null, genre))); - Assert.Null(Merger.Merge(null, null)); - } - - private class TestIOnMerge : IOnMerge - { - public void OnMerge(object other) - { - Exception exception = new(); - exception.Data[0] = other; - throw exception; - } - } - - [Fact] - public void OnMergeTest() - { - TestIOnMerge test = new(); - TestIOnMerge test2 = new(); - Assert.Throws(() => Merger.Merge(test, test2)); - try - { - Merger.Merge(test, test2); - } - catch (Exception ex) - { - Assert.True(ReferenceEquals(test2, ex.Data[0])); - } - } - - private class Test - { - public int ID { get; set; } - - public int[] Numbers { get; set; } - } - - [Fact] - public void GlobalMergeListTest() - { - Test test = new() - { - ID = 5, - Numbers = new [] { 1 } - }; - Test test2 = new() - { - Numbers = new [] { 3 } - }; - Test ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(2, ret.Numbers.Length); - Assert.Equal(1, ret.Numbers[0]); - Assert.Equal(3, ret.Numbers[1]); - } - - [Fact] - public void GlobalMergeListDuplicatesTest() - { - Test test = new() - { - ID = 5, - Numbers = new [] { 1 } - }; - Test test2 = new() - { - Numbers = new [] - { - 1, - 3, - 3 - } - }; - Test ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(4, ret.Numbers.Length); - Assert.Equal(1, ret.Numbers[0]); - Assert.Equal(1, ret.Numbers[1]); - Assert.Equal(3, ret.Numbers[2]); - Assert.Equal(3, ret.Numbers[3]); - } - - [Fact] - public void GlobalMergeListDuplicatesResourcesTest() - { - Show test = new() - { - ID = 5, - Genres = new [] { new Genre("test") } - }; - Show test2 = new() - { - Genres = new [] - { - new Genre("test"), - new Genre("test2") - } - }; - Show ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(2, ret.Genres.Count); - Assert.Equal("test", ret.Genres.ToArray()[0].Slug); - Assert.Equal("test2", ret.Genres.ToArray()[1].Slug); - } - - [Fact] - public void MergeListTest() - { - int[] first = { 1 }; - int[] second = { - 3, - 3 - }; - int[] ret = Merger.MergeLists(first, second); - - Assert.Equal(3, ret.Length); - Assert.Equal(1, ret[0]); - Assert.Equal(3, ret[1]); - Assert.Equal(3, ret[2]); - } - - [Fact] - public void MergeListDuplicateTest() - { - int[] first = { 1 }; - int[] second = { - 1, - 3, - 3 - }; - int[] ret = Merger.MergeLists(first, second); - - Assert.Equal(4, ret.Length); - Assert.Equal(1, ret[0]); - Assert.Equal(1, ret[1]); - Assert.Equal(3, ret[2]); - Assert.Equal(3, ret[3]); - } - - [Fact] - public void MergeListDuplicateCustomEqualityTest() - { - int[] first = { 1 }; - int[] second = { - 3, - 2 - }; - int[] ret = Merger.MergeLists(first, second, (x, y) => x % 2 == y % 2); - - Assert.Equal(2, ret.Length); - Assert.Equal(1, ret[0]); - Assert.Equal(2, ret[1]); - } - } -} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs new file mode 100644 index 00000000..3fe71a89 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using Kyoo.Models; +using TMDbLib.Objects.Search; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The collection to convert. + /// The provider representing TheMovieDb. + /// The converted collection as a . + public static Collection ToCollection(this TMDbLib.Objects.Collections.Collection collection, Provider provider) + { + return new Collection + { + Slug = Utility.ToSlug(collection.Name), + Name = collection.Name, + Overview = collection.Overview, + Images = new Dictionary + { + [Images.Poster] = collection.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" + : null, + [Images.Thumbnail] = collection.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" + : null + }, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/collection/{collection.Id}", + DataID = collection.Id.ToString() + } + } + }; + } + + /// + /// Convert a into a . + /// + /// The collection to convert. + /// The provider representing TheMovieDb. + /// The converted collection as a . + public static Collection ToCollection(this SearchCollection collection, Provider provider) + { + return new Collection + { + Slug = Utility.ToSlug(collection.Name), + Name = collection.Name, + Images = new Dictionary + { + [Images.Poster] = collection.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{collection.PosterPath}" + : null, + [Images.Thumbnail] = collection.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}" + : null + }, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/collection/{collection.Id}", + DataID = collection.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs new file mode 100644 index 00000000..75129af3 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using Kyoo.Models; +using TMDbLib.Objects.TvShows; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The episode to convert. + /// The ID of the show inside TheMovieDb. + /// The provider representing TheMovieDb. + /// The converted episode as a . + public static Episode ToEpisode(this TvEpisode episode, int showID, Provider provider) + { + return new Episode + { + SeasonNumber = episode.SeasonNumber, + EpisodeNumber = episode.EpisodeNumber, + Title = episode.Name, + Overview = episode.Overview, + ReleaseDate = episode.AirDate, + Images = new Dictionary + { + [Images.Thumbnail] = episode.StillPath != null + ? $"https://image.tmdb.org/t/p/original{episode.StillPath}" + : null + }, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/tv/{showID}" + + $"/season/{episode.SeasonNumber}/episode/{episode.EpisodeNumber}", + DataID = episode.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs new file mode 100644 index 00000000..450f6934 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/MovieConvertors.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models; +using TMDbLib.Objects.Movies; +using TMDbLib.Objects.Search; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The movie to convert. + /// The provider representing TheMovieDb. + /// The converted movie as a . + public static Show ToShow(this Movie movie, Provider provider) + { + return new Show + { + Slug = Utility.ToSlug(movie.Title), + Title = movie.Title, + Aliases = movie.AlternativeTitles.Titles.Select(x => x.Title).ToArray(), + Overview = movie.Overview, + Status = movie.Status == "Released" ? Status.Finished : Status.Planned, + StartAir = movie.ReleaseDate, + EndAir = movie.ReleaseDate, + Images = new Dictionary + { + [Images.Poster] = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + [Images.Thumbnail] = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + [Images.Trailer] = movie.Videos?.Results + .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") + .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(), + }, + Genres = movie.Genres.Select(x => new Genre(x.Name)).ToArray(), + Studio = !string.IsNullOrEmpty(movie.ProductionCompanies.FirstOrDefault()?.Name) + ? new Studio(movie.ProductionCompanies.First().Name) + : null, + IsMovie = true, + People = movie.Credits.Cast + .Select(x => x.ToPeople(provider)) + .Concat(movie.Credits.Crew.Select(x => x.ToPeople(provider))) + .ToArray(), + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/movie/{movie.Id}", + DataID = movie.Id.ToString() + } + } + }; + } + + /// + /// Convert a into a . + /// + /// The movie to convert. + /// The provider representing TheMovieDb. + /// The converted movie as a . + public static Show ToShow(this SearchMovie movie, Provider provider) + { + return new Show + { + Slug = Utility.ToSlug(movie.Title), + Title = movie.Title, + Overview = movie.Overview, + StartAir = movie.ReleaseDate, + EndAir = movie.ReleaseDate, + Images = new Dictionary + { + [Images.Poster] = movie.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{movie.PosterPath}" + : null, + [Images.Thumbnail] = movie.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}" + : null, + }, + IsMovie = true, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/movie/{movie.Id}", + DataID = movie.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs new file mode 100644 index 00000000..861f21e0 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs @@ -0,0 +1,182 @@ +using System.Collections.Generic; +using Kyoo.Models; +using TMDbLib.Objects.General; +using TMDbLib.Objects.People; +using TMDbLib.Objects.Search; +using Images = Kyoo.Models.Images; +using TvCast = TMDbLib.Objects.TvShows.Cast; +using MovieCast = TMDbLib.Objects.Movies.Cast; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a to a . + /// + /// An internal TheMovieDB cast. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the movie cast. + public static PeopleRole ToPeople(this MovieCast cast, Provider provider) + { + return new PeopleRole + { + People = new People + { + Slug = Utility.ToSlug(cast.Name), + Name = cast.Name, + Images = new Dictionary + { + [Images.Poster] = cast.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = cast.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{cast.Id}" + } + } + }, + Type = "Actor", + Role = cast.Character + }; + } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB cast. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the movie cast. + public static PeopleRole ToPeople(this TvCast cast, Provider provider) + { + return new PeopleRole + { + People = new People + { + Slug = Utility.ToSlug(cast.Name), + Name = cast.Name, + Images = new Dictionary + { + [Images.Poster] = cast.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = cast.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{cast.Id}" + } + } + }, + Type = "Actor", + Role = cast.Character + }; + } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB crew member. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the movie crew. + public static PeopleRole ToPeople(this Crew crew, Provider provider) + { + return new PeopleRole + { + People = new People + { + Slug = Utility.ToSlug(crew.Name), + Name = crew.Name, + Images = new Dictionary + { + [Images.Poster] = crew.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = crew.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{crew.Id}" + } + } + }, + Type = crew.Department, + Role = crew.Job + }; + } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB person. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the person. + public static People ToPeople(this Person person, Provider provider) + { + return new People + { + Slug = Utility.ToSlug(person.Name), + Name = person.Name, + Images = new Dictionary + { + [Images.Poster] = person.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{person.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = person.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{person.Id}" + } + } + }; + } + + /// + /// Convert a to a . + /// + /// An internal TheMovieDB person. + /// The provider that represent TheMovieDB inside Kyoo. + /// A representing the person. + public static People ToPeople(this SearchPerson person, Provider provider) + { + return new People + { + Slug = Utility.ToSlug(person.Name), + Name = person.Name, + Images = new Dictionary + { + [Images.Poster] = person.ProfilePath != null + ? $"https://image.tmdb.org/t/p/original{person.ProfilePath}" + : null + }, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + DataID = person.Id.ToString(), + Link = $"https://www.themoviedb.org/person/{person.Id}" + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs new file mode 100644 index 00000000..a1aa4e51 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Kyoo.Models; +using TMDbLib.Objects.TvShows; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The season to convert. + /// The ID of the show inside TheMovieDb. + /// The provider representing TheMovieDb. + /// The converted season as a . + public static Season ToSeason(this TvSeason season, int showID, Provider provider) + { + return new Season + { + SeasonNumber = season.SeasonNumber, + Title = season.Name, + Overview = season.Overview, + StartDate = season.AirDate, + Images = new Dictionary + { + [Images.Poster] = season.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{season.PosterPath}" + : null + }, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/tv/{showID}/season/{season.SeasonNumber}", + DataID = season.Id?.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs new file mode 100644 index 00000000..946a8aea --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/ShowConvertors.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models; +using TMDbLib.Objects.Search; +using TMDbLib.Objects.TvShows; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a to a . + /// + /// The show to convert. + /// The provider representing TheMovieDb. + /// A converted as a . + public static Show ToShow(this TvShow tv, Provider provider) + { + return new Show + { + Slug = Utility.ToSlug(tv.Name), + Title = tv.Name, + Aliases = tv.AlternativeTitles.Results.Select(x => x.Title).ToArray(), + Overview = tv.Overview, + Status = tv.Status == "Ended" ? Status.Finished : Status.Planned, + StartAir = tv.FirstAirDate, + EndAir = tv.LastAirDate, + Images = new Dictionary + { + [Images.Poster] = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + [Images.Thumbnail] = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + [Images.Trailer] = tv.Videos?.Results + .Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube") + .Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault() + }, + Genres = tv.Genres.Select(x => new Genre(x.Name)).ToArray(), + Studio = !string.IsNullOrEmpty(tv.ProductionCompanies.FirstOrDefault()?.Name) + ? new Studio(tv.ProductionCompanies.First().Name) + : null, + People = tv.Credits.Cast + .Select(x => x.ToPeople(provider)) + .Concat(tv.Credits.Crew.Select(x => x.ToPeople(provider))) + .ToArray(), + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/tv/{tv.Id}", + DataID = tv.Id.ToString() + } + } + }; + } + + /// + /// Convert a to a . + /// + /// The show to convert. + /// The provider representing TheMovieDb. + /// A converted as a . + public static Show ToShow(this SearchTv tv, Provider provider) + { + return new Show + { + Slug = Utility.ToSlug(tv.Name), + Title = tv.Name, + Overview = tv.Overview, + StartAir = tv.FirstAirDate, + Images = new Dictionary + { + [Images.Poster] = tv.PosterPath != null + ? $"https://image.tmdb.org/t/p/original{tv.PosterPath}" + : null, + [Images.Thumbnail] = tv.BackdropPath != null + ? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}" + : null, + }, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/tv/{tv.Id}", + DataID = tv.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs b/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs new file mode 100644 index 00000000..9839d784 --- /dev/null +++ b/Kyoo.TheMovieDb/Convertors/StudioConvertors.cs @@ -0,0 +1,60 @@ +using Kyoo.Models; +using TMDbLib.Objects.Companies; +using TMDbLib.Objects.Search; + +namespace Kyoo.TheMovieDb +{ + /// + /// A class containing extensions methods to convert from TMDB's types to Kyoo's types. + /// + public static partial class Convertors + { + /// + /// Convert a into a . + /// + /// The company to convert. + /// The provider representing TheMovieDb. + /// The converted company as a . + public static Studio ToStudio(this Company company, Provider provider) + { + return new Studio + { + Slug = Utility.ToSlug(company.Name), + Name = company.Name, + ExternalIDs = new [] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/company/{company.Id}", + DataID = company.Id.ToString() + } + } + }; + } + + /// + /// Convert a into a . + /// + /// The company to convert. + /// The provider representing TheMovieDb. + /// The converted company as a . + public static Studio ToStudio(this SearchCompany company, Provider provider) + { + return new Studio + { + Slug = Utility.ToSlug(company.Name), + Name = company.Name, + ExternalIDs = new[] + { + new MetadataID + { + Provider = provider, + Link = $"https://www.themoviedb.org/company/{company.Id}", + DataID = company.Id.ToString() + } + } + }; + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj b/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj new file mode 100644 index 00000000..99a49716 --- /dev/null +++ b/Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj @@ -0,0 +1,35 @@ + + + net5.0 + + SDG + Zoe Roux + https://github.com/AnonymusRaccoon/Kyoo + default + Kyoo.TheMovieDb + + + + ../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/the-moviedb + false + false + false + false + true + + + + + + + + + + + + all + false + runtime + + + diff --git a/Kyoo.TheMovieDb/PluginTmdb.cs b/Kyoo.TheMovieDb/PluginTmdb.cs new file mode 100644 index 00000000..aaf5aa9c --- /dev/null +++ b/Kyoo.TheMovieDb/PluginTmdb.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using Autofac; +using Kyoo.Controllers; +using Kyoo.Models.Attributes; +using Kyoo.TheMovieDb.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Kyoo.TheMovieDb +{ + /// + /// A plugin that add a for TheMovieDB. + /// + public class PluginTmdb : IPlugin + { + /// + public string Slug => "the-moviedb"; + + /// + public string Name => "TheMovieDb Provider"; + + /// + public string Description => "A metadata provider for TheMovieDB."; + + /// + public ICollection Provides => new [] + { + typeof(IMetadataProvider) + }; + + /// + public ICollection ConditionalProvides => ArraySegment.Empty; + + /// + public ICollection Requires => ArraySegment.Empty; + + + /// + /// The configuration to use. + /// + private readonly IConfiguration _configuration; + + /// + /// The configuration manager used to register typed/untyped implementations. + /// + [Injected] public IConfigurationManager ConfigurationManager { private get; set; } + + + /// + /// Create a new tmdb module instance and use the given configuration. + /// + /// The configuration to use + public PluginTmdb(IConfiguration configuration) + { + _configuration = configuration; + } + + + /// + public void Configure(ContainerBuilder builder) + { + builder.RegisterProvider(); + } + + /// + public void Configure(IServiceCollection services, ICollection availableTypes) + { + services.Configure(_configuration.GetSection(TheMovieDbOptions.Path)); + } + + /// + public void ConfigureAspNet(IApplicationBuilder app) + { + ConfigurationManager.AddTyped(TheMovieDbOptions.Path); + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/ProviderTmdb.cs b/Kyoo.TheMovieDb/ProviderTmdb.cs new file mode 100644 index 00000000..08bc36ea --- /dev/null +++ b/Kyoo.TheMovieDb/ProviderTmdb.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.Controllers; +using Kyoo.Models; +using Kyoo.TheMovieDb.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using TMDbLib.Client; +using TMDbLib.Objects.Movies; +using TMDbLib.Objects.Search; +using TMDbLib.Objects.TvShows; + +namespace Kyoo.TheMovieDb +{ + /// + /// A metadata provider for TheMovieDb. + /// + public class TheMovieDbProvider : IMetadataProvider + { + /// + /// The API key used to authenticate with TheMovieDb API. + /// + private readonly IOptions _apiKey; + /// + /// The logger to use in ase of issue. + /// + private readonly ILogger _logger; + + /// + public Provider Provider => new() + { + Slug = "the-moviedb", + Name = "TheMovieDB", + Images = new Dictionary + { + [Images.Logo] = "https://www.themoviedb.org/assets/2/v4/logos/v2/" + + "blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg" + } + }; + + /// + /// Create a new using the given api key. + /// + /// The api key + /// The logger to use in case of issue. + public TheMovieDbProvider(IOptions apiKey, ILogger logger) + { + _apiKey = apiKey; + _logger = logger; + } + + + /// + public Task Get(T item) + where T : class, IResource + { + return item switch + { + Collection collection => _GetCollection(collection) as Task, + Show show => _GetShow(show) as Task, + Season season => _GetSeason(season) as Task, + Episode episode => _GetEpisode(episode) as Task, + People person => _GetPerson(person) as Task, + Studio studio => _GetStudio(studio) as Task, + _ => null + }; + } + + /// + /// Get a collection using it's id, if the id is not present in the collection, fallback to a name search. + /// + /// The collection to search for + /// A collection containing metadata from TheMovieDb + private async Task _GetCollection(Collection collection) + { + if (!collection.TryGetID(Provider.Slug, out int id)) + { + Collection found = (await _SearchCollections(collection.Name ?? collection.Slug)).FirstOrDefault(); + if (found?.TryGetID(Provider.Slug, out id) != true) + return found; + } + + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.GetCollectionAsync(id)).ToCollection(Provider); + } + + /// + /// Get a show using it's id, if the id is not present in the show, fallback to a title search. + /// + /// The show to search for + /// A show containing metadata from TheMovieDb + private async Task _GetShow(Show show) + { + if (!show.TryGetID(Provider.Slug, out int id)) + { + Show found = (await _SearchShows(show.Title ?? show.Slug, show.StartAir?.Year)) + .FirstOrDefault(x => x.IsMovie == show.IsMovie); + if (found?.TryGetID(Provider.Slug, out id) != true) + return found; + } + + TMDbClient client = new(_apiKey.Value.ApiKey); + + if (show.IsMovie) + { + return (await client + .GetMovieAsync(id, MovieMethods.AlternativeTitles | MovieMethods.Videos | MovieMethods.Credits)) + ?.ToShow(Provider); + } + + return (await client + .GetTvShowAsync(id, TvShowMethods.AlternativeTitles | TvShowMethods.Videos | TvShowMethods.Credits)) + ?.ToShow(Provider); + } + + /// + /// Get a season using it's show and it's season number. + /// + /// The season to retrieve metadata for. + /// A season containing metadata from TheMovieDb + private async Task _GetSeason(Season season) + { + if (season.Show == null) + { + _logger.LogWarning("Metadata for a season was requested but it's show is not loaded. " + + "This is unsupported"); + return null; + } + + if (!season.Show.TryGetID(Provider.Slug, out int id)) + return null; + + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.GetTvSeasonAsync(id, season.SeasonNumber)) + .ToSeason(id, Provider); + } + + /// + /// Get an episode using it's show, it's season number and it's episode number. + /// Absolute numbering is not supported. + /// + /// The episode to retrieve metadata for. + /// An episode containing metadata from TheMovieDb + private async Task _GetEpisode(Episode episode) + { + if (episode.Show == null) + { + _logger.LogWarning("Metadata for an episode was requested but it's show is not loaded. " + + "This is unsupported"); + return null; + } + if (!episode.Show.TryGetID(Provider.Slug, out int id) + || episode.SeasonNumber == null || episode.EpisodeNumber == null) + return null; + + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.GetTvEpisodeAsync(id, episode.SeasonNumber.Value, episode.EpisodeNumber.Value)) + .ToEpisode(id, Provider); + } + + /// + /// Get a person using it's id, if the id is not present in the person, fallback to a name search. + /// + /// The person to search for + /// A person containing metadata from TheMovieDb + private async Task _GetPerson(People person) + { + if (!person.TryGetID(Provider.Slug, out int id)) + { + People found = (await _SearchPeople(person.Name ?? person.Slug)).FirstOrDefault(); + if (found?.TryGetID(Provider.Slug, out id) != true) + return found; + } + + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.GetPersonAsync(id)).ToPeople(Provider); + } + + /// + /// Get a studio using it's id, if the id is not present in the studio, fallback to a name search. + /// + /// The studio to search for + /// A studio containing metadata from TheMovieDb + private async Task _GetStudio(Studio studio) + { + if (!studio.TryGetID(Provider.Slug, out int id)) + { + Studio found = (await _SearchStudios(studio.Name ?? studio.Slug)).FirstOrDefault(); + if (found?.TryGetID(Provider.Slug, out id) != true) + return found; + } + + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.GetCompanyAsync(id)).ToStudio(Provider); + } + + /// + public async Task> Search(string query) + where T : class, IResource + { + if (typeof(T) == typeof(Collection)) + return (await _SearchCollections(query) as ICollection)!; + if (typeof(T) == typeof(Show)) + return (await _SearchShows(query) as ICollection)!; + if (typeof(T) == typeof(People)) + return (await _SearchPeople(query) as ICollection)!; + if (typeof(T) == typeof(Studio)) + return (await _SearchStudios(query) as ICollection)!; + return ArraySegment.Empty; + } + + /// + /// Search for a collection using it's name as a query. + /// + /// The query to search for + /// A list of collections containing metadata from TheMovieDb + private async Task> _SearchCollections(string query) + { + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.SearchCollectionAsync(query)) + .Results + .Select(x => x.ToCollection(Provider)) + .ToArray(); + } + + /// + /// Search for a show using it's name as a query. + /// + /// The query to search for + /// The year in witch the show has aired. + /// A list of shows containing metadata from TheMovieDb + private async Task> _SearchShows(string query, int? year = null) + { + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.SearchMultiAsync(query, year: year ?? 0)) + .Results + .Select(x => + { + return x switch + { + SearchTv tv => tv.ToShow(Provider), + SearchMovie movie => movie.ToShow(Provider), + _ => null + }; + }) + .Where(x => x != null) + .ToArray(); + } + + /// + /// Search for people using there name as a query. + /// + /// The query to search for + /// A list of people containing metadata from TheMovieDb + private async Task> _SearchPeople(string query) + { + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.SearchPersonAsync(query)) + .Results + .Select(x => x.ToPeople(Provider)) + .ToArray(); + } + + /// + /// Search for studios using there name as a query. + /// + /// The query to search for + /// A list of studios containing metadata from TheMovieDb + private async Task> _SearchStudios(string query) + { + TMDbClient client = new(_apiKey.Value.ApiKey); + return (await client.SearchCompanyAsync(query)) + .Results + .Select(x => x.ToStudio(Provider)) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/Kyoo.TheMovieDb/TheMovieDbOptions.cs b/Kyoo.TheMovieDb/TheMovieDbOptions.cs new file mode 100644 index 00000000..2387f553 --- /dev/null +++ b/Kyoo.TheMovieDb/TheMovieDbOptions.cs @@ -0,0 +1,18 @@ +namespace Kyoo.TheMovieDb.Models +{ + /// + /// The option containing the api key for TheMovieDb. + /// + public class TheMovieDbOptions + { + /// + /// The path to get this option from the root configuration. + /// + public const string Path = "the-moviedb"; + + /// + /// The api key of TheMovieDb. + /// + public string ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/Kyoo.TheTvdb/Convertors.cs b/Kyoo.TheTvdb/Convertors.cs index bc31b5fd..69ee4c26 100644 --- a/Kyoo.TheTvdb/Convertors.cs +++ b/Kyoo.TheTvdb/Convertors.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Kyoo.Models; @@ -47,7 +48,7 @@ namespace Kyoo.TheTvdb /// A show representing the given search result. public static Show ToShow(this SeriesSearchResult result, Provider provider) { - return new() + return new Show { Slug = result.Slug, Title = result.SeriesName, @@ -55,14 +56,19 @@ namespace Kyoo.TheTvdb Overview = result.Overview, Status = _GetStatus(result.Status), StartAir = _ParseDate(result.FirstAired), - Poster = result.Poster != null ? $"https://www.thetvdb.com{result.Poster}" : null, + Images = new Dictionary + { + [Images.Poster] = !string.IsNullOrEmpty(result.Poster) + ? $"https://www.thetvdb.com{result.Poster}" + : null, + }, ExternalIDs = new[] { - new MetadataID + new MetadataID { DataID = result.Id.ToString(), Link = $"https://www.thetvdb.com/series/{result.Slug}", - Second = provider + Provider = provider } } }; @@ -76,7 +82,7 @@ namespace Kyoo.TheTvdb /// A show representing the given series. public static Show ToShow(this Series series, Provider provider) { - return new() + return new Show { Slug = series.Slug, Title = series.SeriesName, @@ -84,16 +90,23 @@ namespace Kyoo.TheTvdb Overview = series.Overview, Status = _GetStatus(series.Status), StartAir = _ParseDate(series.FirstAired), - Poster = series.Poster != null ? $"https://www.thetvdb.com/banners/{series.Poster}" : null, - Backdrop = series.FanArt != null ? $"https://www.thetvdb.com/banners/{series.FanArt}" : null, + Images = new Dictionary + { + [Images.Poster] = !string.IsNullOrEmpty(series.Poster) + ? $"https://www.thetvdb.com/banners/{series.Poster}" + : null, + [Images.Thumbnail] = !string.IsNullOrEmpty(series.FanArt) + ? $"https://www.thetvdb.com/banners/{series.FanArt}" + : null + }, Genres = series.Genre.Select(y => new Genre(y)).ToList(), ExternalIDs = new[] { - new MetadataID + new MetadataID { DataID = series.Id.ToString(), Link = $"https://www.thetvdb.com/series/{series.Slug}", - Second = provider + Provider = provider } } }; @@ -103,25 +116,20 @@ namespace Kyoo.TheTvdb /// Convert a tvdb actor to a kyoo . /// /// The actor to convert - /// The provider representing the tvdb inside kyoo /// A people role representing the given actor in the role they played. - public static PeopleRole ToPeopleRole(this Actor actor, Provider provider) + public static PeopleRole ToPeopleRole(this Actor actor) { - return new() + return new PeopleRole { People = new People { Slug = Utility.ToSlug(actor.Name), Name = actor.Name, - Poster = actor.Image != null ? $"https://www.thetvdb.com/banners/{actor.Image}" : null, - ExternalIDs = new [] + Images = new Dictionary { - new MetadataID() - { - DataID = actor.Id.ToString(), - Link = $"https://www.thetvdb.com/people/{actor.Id}", - Second = provider - } + [Images.Poster] = !string.IsNullOrEmpty(actor.Image) + ? $"https://www.thetvdb.com/banners/{actor.Image}" + : null } }, Role = actor.Role, @@ -137,21 +145,26 @@ namespace Kyoo.TheTvdb /// A episode representing the given tvdb episode. public static Episode ToEpisode(this EpisodeRecord episode, Provider provider) { - return new() + return new Episode { SeasonNumber = episode.AiredSeason, EpisodeNumber = episode.AiredEpisodeNumber, AbsoluteNumber = episode.AbsoluteNumber, Title = episode.EpisodeName, Overview = episode.Overview, - Thumb = episode.Filename != null ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null, + Images = new Dictionary + { + [Images.Thumbnail] = !string.IsNullOrEmpty(episode.Filename) + ? $"https://www.thetvdb.com/banners/{episode.Filename}" + : null + }, ExternalIDs = new[] { - new MetadataID + new MetadataID { DataID = episode.Id.ToString(), Link = $"https://www.thetvdb.com/series/{episode.SeriesId}/episodes/{episode.Id}", - Second = provider + Provider = provider } } }; diff --git a/Kyoo.TheTvdb/ProviderTvdb.cs b/Kyoo.TheTvdb/ProviderTvdb.cs index 47183fb7..01411d1d 100644 --- a/Kyoo.TheTvdb/ProviderTvdb.cs +++ b/Kyoo.TheTvdb/ProviderTvdb.cs @@ -32,8 +32,10 @@ namespace Kyoo.TheTvdb { Slug = "the-tvdb", Name = "TheTVDB", - LogoExtension = "png", - Logo = "https://www.thetvdb.com/images/logo.png" + Images = new Dictionary + { + [Images.Logo] = "https://www.thetvdb.com/images/logo.png" + } }; @@ -93,7 +95,7 @@ namespace Kyoo.TheTvdb Show ret = series.Data.ToShow(Provider); TvDbResponse people = await _client.Series.GetActorsAsync(id); - ret.People = people.Data.Select(x => x.ToPeopleRole(Provider)).ToArray(); + ret.People = people.Data.Select(x => x.ToPeopleRole()).ToArray(); return ret; } diff --git a/Kyoo.WebApp b/Kyoo.WebApp index c037270d..dca10903 160000 --- a/Kyoo.WebApp +++ b/Kyoo.WebApp @@ -1 +1 @@ -Subproject commit c037270d3339fcf0075984a089f353c5c332a751 +Subproject commit dca10903ff54a8999732695b5c2a0a5c94f85200 diff --git a/Kyoo.sln b/Kyoo.sln index 55aeb44c..fdb1f643 100644 --- a/Kyoo.sln +++ b/Kyoo.sln @@ -5,8 +5,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Common", "Kyoo.Common\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.CommonAPI", "Kyoo.CommonAPI\Kyoo.CommonAPI.csproj", "{6F91B645-F785-46BB-9C4F-1EFC83E489B6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Tests", "Kyoo.Tests\Kyoo.Tests.csproj", "{D179D5FF-9F75-4B27-8E27-0DBDF1806611}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Postgresql", "Kyoo.Postgresql\Kyoo.Postgresql.csproj", "{3213C96D-0BF3-460B-A8B5-B9977229408A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Authentication", "Kyoo.Authentication\Kyoo.Authentication.csproj", "{7A841335-6523-47DB-9717-80AA7BD943FD}" @@ -15,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.SqLite", "Kyoo.SqLite\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheTvdb", "Kyoo.TheTvdb\Kyoo.TheTvdb.csproj", "{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheMovieDb", "Kyoo.TheMovieDb\Kyoo.TheMovieDb.csproj", "{BAB270D4-E0EA-4329-BA65-512FDAB01001}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Tests", "tests\Kyoo.Tests\Kyoo.Tests.csproj", "{0C8AA7EA-E723-4532-852F-35AA4E8AFED5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,10 +35,6 @@ Global {6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Debug|Any CPU.Build.0 = Debug|Any CPU {6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.Build.0 = Release|Any CPU - {D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Release|Any CPU.Build.0 = Release|Any CPU {3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.Build.0 = Debug|Any CPU {3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -53,5 +51,13 @@ Global {D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.Build.0 = Release|Any CPU + {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.Build.0 = Release|Any CPU + {0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Kyoo.sln.DotSettings b/Kyoo.sln.DotSettings deleted file mode 100644 index f0549f01..00000000 --- a/Kyoo.sln.DotSettings +++ /dev/null @@ -1,11 +0,0 @@ - - Tab - UseExplicitType - UseExplicitType - UseExplicitType - API - DB - True - True - True - True \ No newline at end of file diff --git a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs index da98539f..b3f4f66a 100644 --- a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs +++ b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs @@ -8,7 +8,9 @@ using Autofac.Features.Metadata; using JetBrains.Annotations; using Kyoo.Common.Models.Attributes; using Kyoo.Models; +using Kyoo.Models.Options; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -23,14 +25,31 @@ namespace Kyoo.Controllers /// private readonly ICollection, FileSystemMetadataAttribute>> _fileSystems; + /// + /// The library manager used to load shows to retrieve their path + /// (only if the option is set to metadata in show) + /// + private readonly ILibraryManager _libraryManager; + + /// + /// Options to check if the metadata should be kept in the show directory or in a kyoo's directory. + /// + private readonly IOptionsMonitor _options; + /// /// Create a new from a list of mapped to their /// metadata. /// /// The list of filesystem mapped to their metadata. - public FileSystemComposite(ICollection, FileSystemMetadataAttribute>> fileSystems) + /// The library manager used to load shows to retrieve their path. + /// The options to use. + public FileSystemComposite(ICollection, FileSystemMetadataAttribute>> fileSystems, + ILibraryManager libraryManager, + IOptionsMonitor options) { _fileSystems = fileSystems; + _libraryManager = libraryManager; + _options = options; } @@ -88,6 +107,15 @@ namespace Kyoo.Controllers .GetReader(relativePath); } + /// + public Task GetReader(string path, AsyncRef mime) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + return _GetFileSystemForPath(path, out string relativePath) + .GetReader(relativePath, mime); + } + /// public Task NewFile(string path) { @@ -132,12 +160,41 @@ namespace Kyoo.Controllers } /// - public string GetExtraDirectory(Show show) + public async Task GetExtraDirectory(T resource) { - if (show == null) - throw new ArgumentNullException(nameof(show)); - return _GetFileSystemForPath(show.Path, out string _) - .GetExtraDirectory(show); + switch (resource) + { + case Season season: + await _libraryManager.Load(season, x => x.Show); + break; + case Episode episode: + await _libraryManager.Load(episode, x => x.Show); + break; + case Track track: + await _libraryManager.Load(track, x => x.Episode); + await _libraryManager.Load(track.Episode, x => x.Show); + break; + } + + IFileSystem fs = resource switch + { + Show show => _GetFileSystemForPath(show.Path, out string _), + Season season => _GetFileSystemForPath(season.Show.Path, out string _), + Episode episode => _GetFileSystemForPath(episode.Show.Path, out string _), + Track track => _GetFileSystemForPath(track.Episode.Show.Path, out string _), + _ => _GetFileSystemForPath(_options.CurrentValue.MetadataPath, out string _) + }; + string path = await fs.GetExtraDirectory(resource) + ?? resource switch + { + Season season => await GetExtraDirectory(season.Show), + Episode episode => await GetExtraDirectory(episode.Show), + Track track => await GetExtraDirectory(track.Episode), + IResource res => Combine(_options.CurrentValue.MetadataPath, + typeof(T).Name.ToLowerInvariant(), res.Slug), + _ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLowerInvariant()) + }; + return await CreateDirectory(path); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs index 3d2ea991..c7954db8 100644 --- a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net.Http; using System.Threading.Tasks; using Kyoo.Common.Models.Attributes; -using Kyoo.Models; using Microsoft.AspNetCore.Mvc; namespace Kyoo.Controllers @@ -44,6 +44,16 @@ namespace Kyoo.Controllers HttpClient client = _clientFactory.CreateClient(); return client.GetStreamAsync(path); } + + /// + public async Task GetReader(string path, AsyncRef mime) + { + HttpClient client = _clientFactory.CreateClient(); + HttpResponseMessage response = await client.GetAsync(path); + response.EnsureSuccessStatusCode(); + mime.Value = response.Content.Headers.ContentType?.MediaType; + return await response.Content.ReadAsStreamAsync(); + } /// public Task NewFile(string path) @@ -76,7 +86,7 @@ namespace Kyoo.Controllers } /// - public string GetExtraDirectory(Show show) + public Task GetExtraDirectory(T resource) { throw new NotSupportedException("Extras can not be stored inside an http filesystem."); } @@ -85,6 +95,8 @@ namespace Kyoo.Controllers /// /// An to proxy an http request. /// + // TODO remove this suppress message once the class has been implemented. + [SuppressMessage("ReSharper", "NotAccessedField.Local")] public class HttpForwardResult : IActionResult { /// diff --git a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs index 3239c55f..87575e0a 100644 --- a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs @@ -4,8 +4,10 @@ using System.IO; using System.Threading.Tasks; using Kyoo.Common.Models.Attributes; using Kyoo.Models; +using Kyoo.Models.Options; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -20,6 +22,20 @@ namespace Kyoo.Controllers /// private FileExtensionContentTypeProvider _provider; + /// + /// Options to check if the metadata should be kept in the show directory or in a kyoo's directory. + /// + private readonly IOptionsMonitor _options; + + /// + /// Create a new with the specified options. + /// + /// The options to use. + public LocalFileSystem(IOptionsMonitor options) + { + _options = options; + } + /// /// Get the content type of a file using it's extension. /// @@ -62,6 +78,16 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(path)); return Task.FromResult(File.OpenRead(path)); } + + /// + public Task GetReader(string path, AsyncRef mime) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + _provider.TryGetContentType(path, out string mimeValue); + mime.Value = mimeValue; + return Task.FromResult(File.OpenRead(path)); + } /// public Task NewFile(string path) @@ -104,11 +130,18 @@ namespace Kyoo.Controllers } /// - public string GetExtraDirectory(Show show) + public Task GetExtraDirectory(T resource) { - string path = Path.Combine(show.Path, "Extra"); - Directory.CreateDirectory(path); - return path; + if (!_options.CurrentValue.MetadataInShow) + return Task.FromResult(null); + return Task.FromResult(resource switch + { + Show show => Combine(show.Path, "Extra"), + Season season => Combine(season.Show.Path, "Extra"), + Episode episode => Combine(episode.Show.Path, "Extra"), + Track track => Combine(track.Episode.Show.Path, "Extra"), + _ => null + }); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index e0bc7843..e4250c8f 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -18,6 +18,11 @@ namespace Kyoo.Controllers /// private readonly DatabaseContext _database; + /// + /// A provider repository to handle externalID creation and deletion + /// + private readonly IProviderRepository _providers; + /// protected override Expression> DefaultSort => x => x.Name; @@ -25,17 +30,19 @@ namespace Kyoo.Controllers /// Create a new . /// /// The database handle to use - public CollectionRepository(DatabaseContext database) + /// /// A provider repository + public CollectionRepository(DatabaseContext database, IProviderRepository providers) : base(database) { _database = database; + _providers = providers; } /// public override async Task> Search(string query) { return await _database.Collections - .Where(_database.Like(x => x.Name, $"%{query}%")) + .Where(_database.Like(x => x.Name + " " + x.Slug, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) .ToListAsync(); @@ -49,6 +56,40 @@ namespace Kyoo.Controllers await _database.SaveChangesAsync($"Trying to insert a duplicated collection (slug {obj.Slug} already exists)."); return obj; } + + /// + protected override async Task Validate(Collection resource) + { + await base.Validate(resource); + + if (string.IsNullOrEmpty(resource.Slug)) + throw new ArgumentException("The collection's slug must be set and not empty"); + if (string.IsNullOrEmpty(resource.Name)) + throw new ArgumentException("The collection's name must be set and not empty"); + + if (resource.ExternalIDs != null) + { + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } + } + + /// + protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld) + { + await Validate(changed); + + if (changed.ExternalIDs != null || resetOld) + { + await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); + resource.ExternalIDs = changed.ExternalIDs; + } + } /// public override async Task Delete(Collection obj) diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 3c1393eb..92a573a4 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -99,7 +99,7 @@ namespace Kyoo.Controllers public override async Task> Search(string query) { return await _database.Episodes - .Where(x => x.EpisodeNumber != null) + .Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null) .Where(_database.Like(x => x.Title, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) @@ -111,7 +111,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); return await ValidateTracks(obj); } @@ -119,9 +118,8 @@ namespace Kyoo.Controllers /// protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld) { - if (resource.ShowID <= 0) - throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {resource.ShowID})."); - + await Validate(changed); + if (changed.Tracks != null || resetOld) { await _tracks.DeleteAll(x => x.EpisodeID == resource.ID); @@ -134,8 +132,6 @@ namespace Kyoo.Controllers await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); resource.ExternalIDs = changed.ExternalIDs; } - - await Validate(resource); } /// @@ -145,12 +141,16 @@ namespace Kyoo.Controllers /// The parameter is returned. private async Task ValidateTracks(Episode resource) { - resource.Tracks = await TaskUtils.DefaultIfNull(resource.Tracks?.SelectAsync(x => + if (resource.Tracks == null) + return resource; + + resource.Tracks = await resource.Tracks.SelectAsync(x => { x.Episode = resource; x.EpisodeSlug = resource.Slug; return _tracks.Create(x); - }).ToListAsync()); + }).ToListAsync(); + _database.Tracks.AttachRange(resource.Tracks); return resource; } @@ -158,12 +158,24 @@ namespace Kyoo.Controllers protected override async Task Validate(Episode resource) { await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async x => - { - x.Second = await _providers.CreateIfNotExists(x.Second); - x.SecondID = x.Second.ID; - _database.Entry(x.Second).State = EntityState.Detached; - }); + if (resource.ShowID <= 0) + { + if (resource.Show == null) + throw new ArgumentException($"Can't store an episode not related " + + $"to any show (showID: {resource.ShowID})."); + resource.ShowID = resource.Show.ID; + } + + if (resource.ExternalIDs != null) + { + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } } /// diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index b7b782bc..06f57fc1 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -43,7 +43,7 @@ namespace Kyoo.Controllers public override async Task> Search(string query) { return await _database.Libraries - .Where(_database.Like(x => x.Name, $"%{query}%")) + .Where(_database.Like(x => x.Name + " " + x.Slug, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) .ToListAsync(); @@ -54,7 +54,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ProviderLinks.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); return obj; } @@ -63,30 +62,30 @@ namespace Kyoo.Controllers protected override async Task Validate(Library resource) { await base.Validate(resource); - resource.ProviderLinks = resource.Providers? - .Select(x => Link.Create(resource, x)) - .ToList(); - await resource.ProviderLinks.ForEachAsync(async id => - { - id.Second = await _providers.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; - }); - } - - /// - protected override async Task EditRelations(Library resource, Library changed, bool resetOld) - { + if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("The library's slug must be set and not empty"); if (string.IsNullOrEmpty(resource.Name)) throw new ArgumentException("The library's name must be set and not empty"); if (resource.Paths == null || !resource.Paths.Any()) throw new ArgumentException("The library should have a least one path."); + + if (resource.Providers != null) + { + resource.Providers = await resource.Providers + .SelectAsync(x => _providers.CreateIfNotExists(x)) + .ToListAsync(); + _database.AttachRange(resource.Providers); + } + } + + /// + protected override async Task EditRelations(Library resource, Library changed, bool resetOld) + { + await Validate(changed); if (changed.Providers != null || resetOld) { - await Validate(changed); await Database.Entry(resource).Collection(x => x.Providers).LoadAsync(); resource.Providers = changed.Providers; } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 9adb37ee..3b6d3f87 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -62,7 +62,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); return obj; } @@ -71,23 +70,35 @@ namespace Kyoo.Controllers protected override async Task Validate(People resource) { await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async id => + + if (resource.ExternalIDs != null) { - id.Second = await _providers.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; - }); - await resource.Roles.ForEachAsync(async role => + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } + + if (resource.Roles != null) { - role.Show = await _shows.Value.CreateIfNotExists(role.Show); - role.ShowID = role.Show.ID; - _database.Entry(role.Show).State = EntityState.Detached; - }); + foreach (PeopleRole role in resource.Roles) + { + role.Show = _database.LocalEntity(role.Show.Slug) + ?? await _shows.Value.CreateIfNotExists(role.Show); + role.ShowID = role.Show.ID; + _database.Entry(role).State = EntityState.Added; + } + } } /// protected override async Task EditRelations(People resource, People changed, bool resetOld) { + await Validate(changed); + if (changed.Roles != null || resetOld) { await Database.Entry(resource).Collection(x => x.Roles).LoadAsync(); @@ -98,9 +109,7 @@ namespace Kyoo.Controllers { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); resource.ExternalIDs = changed.ExternalIDs; - } - await base.EditRelations(resource, changed, resetOld); } /// diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index e48d4f50..f555f5a5 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -9,21 +9,18 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { /// - /// A local repository to handle providers. + /// A local repository to handle providers. /// public class ProviderRepository : LocalRepository, IProviderRepository { /// - /// The database handle + /// The database handle /// private readonly DatabaseContext _database; - - /// - protected override Expression> DefaultSort => x => x.Slug; /// - /// Create a new . + /// Create a new . /// /// The database handle public ProviderRepository(DatabaseContext database) @@ -32,6 +29,9 @@ namespace Kyoo.Controllers _database = database; } + /// + protected override Expression> DefaultSort => x => x.Slug; + /// public override async Task> Search(string query) { @@ -47,7 +47,8 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated provider (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync("Trying to insert a duplicated provider " + + $"(slug {obj.Slug} already exists)."); return obj; } @@ -56,20 +57,21 @@ namespace Kyoo.Controllers { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); } /// - public Task>> GetMetadataID(Expression, bool>> where = null, - Sort> sort = default, + public Task> GetMetadataID(Expression> where = null, + Sort sort = default, Pagination limit = default) - where T : class, IResource + where T : class, IMetadata { - return ApplyFilters(_database.MetadataIds().Include(y => y.Second), - x => _database.MetadataIds().FirstOrDefaultAsync(y => y.FirstID == x), - x => x.FirstID, + return ApplyFilters(_database.MetadataIds() + .Include(y => y.Provider), + x => _database.MetadataIds().FirstOrDefaultAsync(y => y.ResourceID == x), + x => x.ResourceID, where, sort, limit); diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index e0036982..0ac7e759 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -87,7 +87,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); return obj; } @@ -95,32 +94,37 @@ namespace Kyoo.Controllers /// protected override async Task Validate(Season resource) { + await base.Validate(resource); if (resource.ShowID <= 0) { if (resource.Show == null) - throw new InvalidOperationException( + throw new ArgumentException( $"Can't store a season not related to any show (showID: {resource.ShowID})."); resource.ShowID = resource.Show.ID; } - await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async id => + if (resource.ExternalIDs != null) { - id.Second = await _providers.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; - }); + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } } /// protected override async Task EditRelations(Season resource, Season changed, bool resetOld) { + await Validate(changed); + if (changed.ExternalIDs != null || resetOld) { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); resource.ExternalIDs = changed.ExternalIDs; } - await base.EditRelations(resource, changed, resetOld); } /// diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 769cb232..3ef8747c 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -76,9 +76,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.GenreLinks.ForEach(x => _database.Entry(x).State = EntityState.Added); - obj.People.ForEach(x => _database.Entry(x).State = EntityState.Added); - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); return obj; } @@ -88,29 +85,40 @@ namespace Kyoo.Controllers { await base.Validate(resource); if (resource.Studio != null) + { resource.Studio = await _studios.CreateIfNotExists(resource.Studio); + resource.StudioID = resource.Studio.ID; + } - resource.GenreLinks = resource.Genres? - .Select(x => Link.Create(resource, x)) - .ToList(); - await resource.GenreLinks.ForEachAsync(async id => + if (resource.Genres != null) { - id.Second = await _genres.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; - }); - await resource.ExternalIDs.ForEachAsync(async id => + resource.Genres = await resource.Genres + .SelectAsync(x => _genres.CreateIfNotExists(x)) + .ToListAsync(); + _database.AttachRange(resource.Genres); + } + + if (resource.ExternalIDs != null) { - id.Second = await _providers.CreateIfNotExists(id.Second); - id.SecondID = id.Second.ID; - _database.Entry(id.Second).State = EntityState.Detached; - }); - await resource.People.ForEachAsync(async role => + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } + + if (resource.People != null) { - role.People = await _people.CreateIfNotExists(role.People); - role.PeopleID = role.People.ID; - _database.Entry(role.People).State = EntityState.Detached; - }); + foreach (PeopleRole role in resource.People) + { + role.People = _database.LocalEntity(role.People.Slug) + ?? await _people.CreateIfNotExists(role.People); + role.PeopleID = role.People.ID; + _database.Entry(role).State = EntityState.Added; + } + } } /// @@ -151,21 +159,18 @@ namespace Kyoo.Controllers { if (collectionID != null) { - await _database.Links() - .AddAsync(new Link(collectionID.Value, showID)); + await _database.AddLinks(collectionID.Value, showID); await _database.SaveIfNoDuplicates(); if (libraryID != null) { - await _database.Links() - .AddAsync(new Link(libraryID.Value, collectionID.Value)); + await _database.AddLinks(libraryID.Value, collectionID.Value); await _database.SaveIfNoDuplicates(); } } if (libraryID != null) { - await _database.Links() - .AddAsync(new Link(libraryID.Value, showID)); + await _database.AddLinks(libraryID.Value, showID); await _database.SaveIfNoDuplicates(); } } diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 516b7c08..2c807375 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -18,6 +18,11 @@ namespace Kyoo.Controllers /// private readonly DatabaseContext _database; + /// + /// A provider repository to handle externalID creation and deletion + /// + private readonly IProviderRepository _providers; + /// protected override Expression> DefaultSort => x => x.Name; @@ -26,10 +31,12 @@ namespace Kyoo.Controllers /// Create a new . /// /// The database handle - public StudioRepository(DatabaseContext database) + /// A provider repository + public StudioRepository(DatabaseContext database, IProviderRepository providers) : base(database) { _database = database; + _providers = providers; } /// @@ -50,6 +57,34 @@ namespace Kyoo.Controllers await _database.SaveChangesAsync($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); return obj; } + + /// + protected override async Task Validate(Studio resource) + { + await base.Validate(resource); + if (resource.ExternalIDs != null) + { + foreach (MetadataID id in resource.ExternalIDs) + { + id.Provider = _database.LocalEntity(id.Provider.Slug) + ?? await _providers.CreateIfNotExists(id.Provider); + id.ProviderID = id.Provider.ID; + } + _database.MetadataIds().AttachRange(resource.ExternalIDs); + } + } + + /// + protected override async Task EditRelations(Studio resource, Studio changed, bool resetOld) + { + if (changed.ExternalIDs != null || resetOld) + { + await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); + resource.ExternalIDs = changed.ExternalIDs; + } + + await base.EditRelations(resource, changed, resetOld); + } /// public override async Task Delete(Studio obj) diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 9b642a93..a462ca23 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -37,19 +37,25 @@ namespace Kyoo.Controllers throw new InvalidOperationException("Tracks do not support the search method."); } + /// + protected override async Task Validate(Track resource) + { + await base.Validate(resource); + if (resource.EpisodeID <= 0) + { + resource.EpisodeID = resource.Episode?.ID ?? 0; + if (resource.EpisodeID <= 0) + throw new ArgumentException("Can't store a track not related to any episode " + + $"(episodeID: {resource.EpisodeID})."); + } + } + /// public override async Task Create(Track obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); - if (obj.EpisodeID <= 0) - { - obj.EpisodeID = obj.Episode?.ID ?? 0; - if (obj.EpisodeID <= 0) - throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID})."); - } - await base.Create(obj); _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(); diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 389891b3..23469dcf 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -1,11 +1,10 @@ using Kyoo.Models; using System; using System.IO; +using System.Linq; using System.Threading.Tasks; -using JetBrains.Annotations; -using Kyoo.Models.Options; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -22,54 +21,17 @@ namespace Kyoo.Controllers /// A logger to report errors. /// private readonly ILogger _logger; - /// - /// The options containing the base path of people images and provider logos. - /// - private readonly IOptionsMonitor _options; - /// - /// A library manager used to load episode and seasons shows if they are not loaded. - /// - private readonly Lazy _library; /// /// Create a new . /// /// The file manager to use. /// A logger to report errors - /// The options to use. - /// A library manager used to load shows if they are not loaded. public ThumbnailsManager(IFileSystem files, - ILogger logger, - IOptionsMonitor options, - Lazy library) + ILogger logger) { _files = files; _logger = logger; - _options = options; - _library = library; - - options.OnChange(x => - { - _files.CreateDirectory(x.PeoplePath); - _files.CreateDirectory(x.ProviderPath); - }); - } - - /// - public Task DownloadImages(T item, bool alwaysDownload = false) - where T : IResource - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - return item switch - { - Show show => _Validate(show, alwaysDownload), - Season season => _Validate(season, alwaysDownload), - Episode episode => _Validate(episode, alwaysDownload), - People people => _Validate(people, alwaysDownload), - Provider provider => _Validate(provider, alwaysDownload), - _ => Task.FromResult(false) - }; } /// @@ -86,8 +48,12 @@ namespace Kyoo.Controllers try { - await using Stream reader = await _files.GetReader(url); - await using Stream local = await _files.NewFile(localPath); + AsyncRef mime = new(); + await using Stream reader = await _files.GetReader(url, mime); + string extension = new FileExtensionContentTypeProvider() + .Mappings.FirstOrDefault(x => x.Value == mime.Value) + .Key; + await using Stream local = await _files.NewFile(localPath + extension); await reader.CopyToAsync(local); return true; } @@ -98,195 +64,74 @@ namespace Kyoo.Controllers } } - /// - /// Download images of a specified show. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Show show, bool alwaysDownload) + /// + public async Task DownloadImages(T item, bool alwaysDownload = false) + where T : IThumbnails { - bool ret = false; - - if (show.Poster != null) - { - string posterPath = await GetPoster(show); - if (alwaysDownload || !await _files.Exists(posterPath)) - ret |= await _DownloadImage(show.Poster, posterPath, $"The poster of {show.Title}"); - } - if (show.Logo != null) - { - string logoPath = await GetLogo(show); - if (alwaysDownload || !await _files.Exists(logoPath)) - ret |= await _DownloadImage(show.Logo, logoPath, $"The logo of {show.Title}"); - } - if (show.Backdrop != null) - { - string backdropPath = await GetThumbnail(show); - if (alwaysDownload || !await _files.Exists(backdropPath)) - ret |= await _DownloadImage(show.Backdrop, backdropPath, $"The backdrop of {show.Title}"); - } + if (item == null) + throw new ArgumentNullException(nameof(item)); + if (item.Images == null) + return false; + + string name = item is IResource res ? res.Slug : "???"; + bool ret = false; + + foreach ((int id, string image) in item.Images.Where(x => x.Value != null)) + { + string localPath = await _GetPrivateImagePath(item, id); + if (alwaysDownload || !await _files.Exists(localPath)) + ret |= await _DownloadImage(image, localPath, $"The image n°{id} of {name}"); + } + return ret; } /// - /// Download images of a specified person. + /// Retrieve the local path of an image of the given item without an extension. /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] People people, bool alwaysDownload) - { - if (people == null) - throw new ArgumentNullException(nameof(people)); - if (people.Poster == null) - return false; - string localPath = await GetPoster(people); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(people.Poster, localPath, $"The profile picture of {people.Name}"); - return false; - } - - /// - /// Download images of a specified season. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Season season, bool alwaysDownload) - { - if (season.Poster == null) - return false; - - string localPath = await GetPoster(season); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(season.Poster, localPath, $"The poster of {season.Slug}"); - return false; - } - - /// - /// Download images of a specified episode. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Episode episode, bool alwaysDownload) - { - if (episode.Thumb == null) - return false; - - string localPath = await _GetEpisodeThumb(episode); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}"); - return false; - } - - /// - /// Download images of a specified provider. - /// - /// - /// The item to cache images. - /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// - /// true if an image has been downloaded, false otherwise. - private async Task _Validate([NotNull] Provider provider, bool alwaysDownload) - { - if (provider.Logo == null) - return false; - - string localPath = await GetLogo(provider); - if (alwaysDownload || !await _files.Exists(localPath)) - return await _DownloadImage(provider.Logo, localPath, $"The logo of {provider.Slug}"); - return false; - } - - /// - public Task GetPoster(T item) - where T : IResource + /// The item to retrieve the poster from. + /// The ID of the image. See for values. + /// The type of the item + /// The path of the image for the given resource, even if it does not exists + private async Task _GetPrivateImagePath(T item, int imageID) { if (item == null) throw new ArgumentNullException(nameof(item)); - return item switch + + string directory = await _files.GetExtraDirectory(item); + string imageName = imageID switch { - Show show => Task.FromResult(_files.Combine(_files.GetExtraDirectory(show), "poster.jpg")), - Season season => _GetSeasonPoster(season), - People actor => Task.FromResult(_files.Combine(_options.CurrentValue.PeoplePath, $"{actor.Slug}.jpg")), - _ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a poster.") + Images.Poster => "poster", + Images.Logo => "logo", + Images.Thumbnail => "thumbnail", + Images.Trailer => "trailer", + _ => $"{imageID}" }; - } + + switch (item) + { + case Season season: + imageName = $"season-{season.SeasonNumber}-{imageName}"; + break; + case Episode episode: + directory = await _files.CreateDirectory(_files.Combine(directory, "Thumbnails")); + imageName = $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"; + break; + } - /// - /// Retrieve the path of a season's poster. - /// - /// The season to retrieve the poster from. - /// The path of the season's poster. - private async Task _GetSeasonPoster(Season season) - { - if (season.Show == null) - await _library.Value.Load(season, x => x.Show); - return _files.Combine(_files.GetExtraDirectory(season.Show), $"season-{season.SeasonNumber}.jpg"); + return _files.Combine(directory, imageName); } /// - public Task GetThumbnail(T item) - where T : IResource + public async Task GetImagePath(T item, int imageID) + where T : IThumbnails { - if (item == null) - throw new ArgumentNullException(nameof(item)); - return item switch - { - Show show => Task.FromResult(_files.Combine(_files.GetExtraDirectory(show), "backdrop.jpg")), - Episode episode => _GetEpisodeThumb(episode), - _ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a thumbnail.") - }; - } - - /// - /// Get the path for an episode's thumbnail. - /// - /// The episode to retrieve the thumbnail from - /// The path of the given episode's thumbnail. - private async Task _GetEpisodeThumb(Episode episode) - { - if (episode.Show == null) - await _library.Value.Load(episode, x => x.Show); - string dir = _files.Combine(_files.GetExtraDirectory(episode.Show), "Thumbnails"); - await _files.CreateDirectory(dir); - return _files.Combine(dir, $"{Path.GetFileNameWithoutExtension(episode.Path)}.jpg"); - } - - /// - public Task GetLogo(T item) - where T : IResource - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - return Task.FromResult(item switch - { - Show show => _files.Combine(_files.GetExtraDirectory(show), "logo.png"), - Provider provider => _files.Combine(_options.CurrentValue.ProviderPath, - $"{provider.Slug}.{provider.LogoExtension}"), - _ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a thumbnail.") - }); + string basePath = await _GetPrivateImagePath(item, imageID); + string directory = Path.GetDirectoryName(basePath); + string baseFile = Path.GetFileName(basePath); + return (await _files.ListFiles(directory!)) + .FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == baseFile); } } } diff --git a/Kyoo/Controllers/Transcoder.cs b/Kyoo/Controllers/Transcoder.cs index 47291753..5942cbd2 100644 --- a/Kyoo/Controllers/Transcoder.cs +++ b/Kyoo/Controllers/Transcoder.cs @@ -88,10 +88,8 @@ namespace Kyoo.Controllers public async Task ExtractInfos(Episode episode, bool reextract) { - if (episode.Show == null) - await _library.Value.Load(episode, x => x.Show); - - string dir = _files.GetExtraDirectory(episode.Show); + await _library.Value.Load(episode, x => x.Show); + string dir = await _files.GetExtraDirectory(episode.Show); if (dir == null) throw new ArgumentException("Invalid path."); return await Task.Factory.StartNew( diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs index 2f5c7cfb..51f4f94c 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -101,13 +101,13 @@ namespace Kyoo /// public void Configure(ContainerBuilder builder) { - builder.RegisterComposite(); + builder.RegisterComposite().InstancePerLifetimeScope(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().SingleInstance(); diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 70fae8ca..e7f4f1b4 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -40,7 +40,7 @@ - + diff --git a/Kyoo/Models/Options/BasicOptions.cs b/Kyoo/Models/Options/BasicOptions.cs index 95d4873a..60e95ee2 100644 --- a/Kyoo/Models/Options/BasicOptions.cs +++ b/Kyoo/Models/Options/BasicOptions.cs @@ -25,16 +25,6 @@ namespace Kyoo.Models.Options /// public string PluginPath { get; set; } = "plugins/"; - /// - /// The path of the people pictures. - /// - public string PeoplePath { get; set; } = "people/"; - - /// - /// The path of providers icons. - /// - public string ProviderPath { get; set; } = "providers/"; - /// /// The temporary folder to cache transmuxed file. /// @@ -44,5 +34,22 @@ namespace Kyoo.Models.Options /// The temporary folder to cache transcoded file. /// public string TranscodePath { get; set; } = "cached/transcode"; + + /// + /// true if the metadata of a show/season/episode should be stored in the same directory as video files, + /// false to save them in a kyoo specific directory. + /// + /// + /// Some file systems might discard this option to store them somewhere else. + /// For example, readonly file systems will probably store them in a kyoo specific directory. + /// + public bool MetadataInShow { get; set; } = true; + + /// + /// The path for metadata if they are not stored near show (see ). + /// Some resources can't be stored near a show and they are stored in this directory + /// (like ). + /// + public string MetadataPath { get; set; } = "metadata/"; } } \ No newline at end of file diff --git a/Kyoo/Tasks/RegisterEpisode.cs b/Kyoo/Tasks/RegisterEpisode.cs index 9df8b1f7..c7c7c3e3 100644 --- a/Kyoo/Tasks/RegisterEpisode.cs +++ b/Kyoo/Tasks/RegisterEpisode.cs @@ -116,16 +116,11 @@ namespace Kyoo.Tasks else show = registeredShow; - // If they are not already loaded, load external ids to allow metadata providers to use them. - if (show.ExternalIDs == null) - await _libraryManager.Load(show, x => x.ExternalIDs); progress.Report(50); if (season != null) season.Show = show; season = await _RegisterAndFill(season); - if (season != null) - season.Title ??= $"Season {season.SeasonNumber}"; progress.Report(60); episode.Show = show; @@ -163,16 +158,32 @@ namespace Kyoo.Tasks /// The type of the item /// The existing or filled item. private async Task _RegisterAndFill(T item) - where T : class, IResource + where T : class, IResource, IThumbnails, IMetadata { if (item == null || string.IsNullOrEmpty(item.Slug)) return null; T existing = await _libraryManager.GetOrDefault(item.Slug); if (existing != null) + { + await _libraryManager.Load(existing, x => x.ExternalIDs); return existing; + } + item = await _metadataProvider.Get(item); await _thumbnailsManager.DownloadImages(item); + + switch (item) + { + case Show show when show.People != null: + foreach (PeopleRole role in show.People) + await _thumbnailsManager.DownloadImages(role.People); + break; + case Season season: + season.Title ??= $"Season {season.SeasonNumber}"; + break; + } + return await _libraryManager.CreateIfNotExists(item); } } diff --git a/Kyoo/Views/CollectionApi.cs b/Kyoo/Views/CollectionApi.cs index 783bb67b..9c26a65c 100644 --- a/Kyoo/Views/CollectionApi.cs +++ b/Kyoo/Views/CollectionApi.cs @@ -6,6 +6,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Kyoo.CommonApi; +using Kyoo.Models.Exceptions; using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Microsoft.Extensions.Options; @@ -19,11 +20,18 @@ namespace Kyoo.Api public class CollectionApi : CrudApi { private readonly ILibraryManager _libraryManager; - - public CollectionApi(ILibraryManager libraryManager, IOptions options) + private readonly IFileSystem _files; + private readonly IThumbnailsManager _thumbs; + + public CollectionApi(ILibraryManager libraryManager, + IFileSystem files, + IThumbnailsManager thumbs, + IOptions options) : base(libraryManager.CollectionRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; + _files = files; + _thumbs = thumbs; } [HttpGet("{id:int}/show")] @@ -129,5 +137,48 @@ namespace Kyoo.Api return BadRequest(new {Error = ex.Message}); } } + + [HttpGet("{slug}/poster")] + public async Task GetPoster(string slug) + { + try + { + Collection collection = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Poster)); + } + catch (ItemNotFoundException) + { + return NotFound(); + } + } + + [HttpGet("{slug}/logo")] + public async Task GetLogo(string slug) + { + try + { + Collection collection = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Logo)); + } + catch (ItemNotFoundException) + { + return NotFound(); + } + } + + [HttpGet("{slug}/backdrop")] + [HttpGet("{slug}/thumbnail")] + public async Task GetBackdrop(string slug) + { + try + { + Collection collection = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Thumbnail)); + } + catch (ItemNotFoundException) + { + return NotFound(); + } + } } } \ No newline at end of file diff --git a/Kyoo/Views/EpisodeApi.cs b/Kyoo/Views/EpisodeApi.cs index b7d83e27..e98f1f6a 100644 --- a/Kyoo/Views/EpisodeApi.cs +++ b/Kyoo/Views/EpisodeApi.cs @@ -195,7 +195,7 @@ namespace Kyoo.Api try { Episode episode = await _libraryManager.Get(id); - return _files.FileResult(await _thumbnails.GetThumbnail(episode)); + return _files.FileResult(await _thumbnails.GetImagePath(episode, Images.Thumbnail)); } catch (ItemNotFoundException) { @@ -210,7 +210,7 @@ namespace Kyoo.Api try { Episode episode = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbnails.GetThumbnail(episode)); + return _files.FileResult(await _thumbnails.GetImagePath(episode, Images.Thumbnail)); } catch (ItemNotFoundException) { diff --git a/Kyoo/Views/PeopleApi.cs b/Kyoo/Views/PeopleApi.cs index bb757a60..bd25491e 100644 --- a/Kyoo/Views/PeopleApi.cs +++ b/Kyoo/Views/PeopleApi.cs @@ -94,7 +94,7 @@ namespace Kyoo.Api People people = await _libraryManager.GetOrDefault(id); if (people == null) return NotFound(); - return _files.FileResult(await _thumbs.GetPoster(people)); + return _files.FileResult(await _thumbs.GetImagePath(people, Images.Poster)); } [HttpGet("{slug}/poster")] @@ -103,7 +103,7 @@ namespace Kyoo.Api People people = await _libraryManager.GetOrDefault(slug); if (people == null) return NotFound(); - return _files.FileResult(await _thumbs.GetPoster(people)); + return _files.FileResult(await _thumbs.GetImagePath(people, Images.Poster)); } } } \ No newline at end of file diff --git a/Kyoo/Views/ProviderApi.cs b/Kyoo/Views/ProviderApi.cs index 026b79ef..5651f4f7 100644 --- a/Kyoo/Views/ProviderApi.cs +++ b/Kyoo/Views/ProviderApi.cs @@ -36,7 +36,7 @@ namespace Kyoo.Api Provider provider = await _libraryManager.GetOrDefault(id); if (provider == null) return NotFound(); - return _files.FileResult(await _thumbnails.GetLogo(provider)); + return _files.FileResult(await _thumbnails.GetImagePath(provider, Images.Logo)); } [HttpGet("{slug}/logo")] @@ -45,7 +45,7 @@ namespace Kyoo.Api Provider provider = await _libraryManager.GetOrDefault(slug); if (provider == null) return NotFound(); - return _files.FileResult(await _thumbnails.GetLogo(provider)); + return _files.FileResult(await _thumbnails.GetImagePath(provider, Images.Logo)); } } } \ No newline at end of file diff --git a/Kyoo/Views/SeasonApi.cs b/Kyoo/Views/SeasonApi.cs index 8d08dd0c..fdf98959 100644 --- a/Kyoo/Views/SeasonApi.cs +++ b/Kyoo/Views/SeasonApi.cs @@ -151,7 +151,7 @@ namespace Kyoo.Api if (season == null) return NotFound(); await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetPoster(season)); + return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster)); } [HttpGet("{slug}/poster")] @@ -161,7 +161,7 @@ namespace Kyoo.Api if (season == null) return NotFound(); await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetPoster(season)); + return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster)); } } } \ No newline at end of file diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index 04854849..0c8dde65 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -383,7 +383,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments"); + string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments"); return (await _files.ListFiles(path)) .ToDictionary(Path.GetFileNameWithoutExtension, x => $"{BaseURL}api/shows/{slug}/fonts/{Path.GetFileName(x)}"); @@ -402,7 +402,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(showSlug); - string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments", slug); + string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments", slug); return _files.FileResult(path); } catch (ItemNotFoundException) @@ -417,7 +417,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetPoster(show)); + return _files.FileResult(await _thumbs.GetImagePath(show, Images.Poster)); } catch (ItemNotFoundException) { @@ -431,7 +431,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetLogo(show)); + return _files.FileResult(await _thumbs.GetImagePath(show, Images.Logo)); } catch (ItemNotFoundException) { @@ -446,7 +446,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - return _files.FileResult(await _thumbs.GetThumbnail(show)); + return _files.FileResult(await _thumbs.GetImagePath(show, Images.Thumbnail)); } catch (ItemNotFoundException) { diff --git a/Kyoo/settings.json b/Kyoo/settings.json index 66ff8566..1ddc8cf1 100644 --- a/Kyoo/settings.json +++ b/Kyoo/settings.json @@ -3,10 +3,10 @@ "url": "http://*:5000", "publicUrl": "http://localhost:5000/", "pluginsPath": "plugins/", - "peoplePath": "people/", - "providerPath": "providers/", "transmuxPath": "cached/transmux", - "transcodePath": "cached/transcode" + "transcodePath": "cached/transcode", + "metadataInShow": true, + "metadataPath": "metadata/" }, "database": { @@ -70,5 +70,8 @@ "tvdb": { "apiKey": "REDACTED" + }, + "the-moviedb": { + "apiKey": "REDACTED" } } diff --git a/tests/Kyoo.Tests/Database/RepositoryActivator.cs b/tests/Kyoo.Tests/Database/RepositoryActivator.cs new file mode 100644 index 00000000..8e543546 --- /dev/null +++ b/tests/Kyoo.Tests/Database/RepositoryActivator.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Kyoo.Controllers; +using Xunit.Abstractions; + +namespace Kyoo.Tests +{ + public class RepositoryActivator : IDisposable, IAsyncDisposable + { + public TestContext Context { get; } + public ILibraryManager LibraryManager { get; } + + + private readonly List _databases = new(); + + public RepositoryActivator(ITestOutputHelper output, PostgresFixture postgres = null) + { + Context = postgres == null + ? new SqLiteTestContext(output) + : new PostgresTestContext(postgres, output); + + ProviderRepository provider = new(_NewContext()); + LibraryRepository library = new(_NewContext(), provider); + CollectionRepository collection = new(_NewContext(), provider); + GenreRepository genre = new(_NewContext()); + StudioRepository studio = new(_NewContext(), provider); + PeopleRepository people = new(_NewContext(), provider, + new Lazy(() => LibraryManager.ShowRepository)); + ShowRepository show = new(_NewContext(), studio, people, genre, provider); + SeasonRepository season = new(_NewContext(), provider); + LibraryItemRepository libraryItem = new(_NewContext(), + new Lazy(() => LibraryManager.LibraryRepository)); + TrackRepository track = new(_NewContext()); + EpisodeRepository episode = new(_NewContext(), provider, track); + UserRepository user = new(_NewContext()); + + LibraryManager = new LibraryManager(new IBaseRepository[] { + provider, + library, + libraryItem, + collection, + show, + season, + episode, + track, + people, + studio, + genre, + user + }); + } + + private DatabaseContext _NewContext() + { + DatabaseContext context = Context.New(); + _databases.Add(context); + return context; + } + + public void Dispose() + { + foreach (DatabaseContext context in _databases) + context.Dispose(); + Context.Dispose(); + GC.SuppressFinalize(this); + } + + public async ValueTask DisposeAsync() + { + foreach (DatabaseContext context in _databases) + await context.DisposeAsync(); + await Context.DisposeAsync(); + } + } +} \ No newline at end of file diff --git a/Kyoo.Tests/Database/RepositoryTests.cs b/tests/Kyoo.Tests/Database/RepositoryTests.cs similarity index 100% rename from Kyoo.Tests/Database/RepositoryTests.cs rename to tests/Kyoo.Tests/Database/RepositoryTests.cs diff --git a/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs new file mode 100644 index 00000000..f1f773ad --- /dev/null +++ b/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.Controllers; +using Kyoo.Models; +using Microsoft.EntityFrameworkCore; +using Xunit; +using Xunit.Abstractions; + +namespace Kyoo.Tests.Database +{ + namespace SqLite + { + public class CollectionTests : ACollectionTests + { + public CollectionTests(ITestOutputHelper output) + : base(new RepositoryActivator(output)) { } + } + } + + namespace PostgreSQL + { + [Collection(nameof(Postgresql))] + public class CollectionTests : ACollectionTests + { + public CollectionTests(PostgresFixture postgres, ITestOutputHelper output) + : base(new RepositoryActivator(output, postgres)) { } + } + } + + public abstract class ACollectionTests : RepositoryTests + { + private readonly ICollectionRepository _repository; + + protected ACollectionTests(RepositoryActivator repositories) + : base(repositories) + { + _repository = Repositories.LibraryManager.CollectionRepository; + } + + [Fact] + public async Task CreateWithEmptySlugTest() + { + Collection collection = TestSample.GetNew(); + collection.Slug = ""; + await Assert.ThrowsAsync(() => _repository.Create(collection)); + } + + [Fact] + public async Task CreateWithNumberSlugTest() + { + Collection collection = TestSample.GetNew(); + collection.Slug = "2"; + Collection ret = await _repository.Create(collection); + Assert.Equal("2!", ret.Slug); + } + + [Fact] + public async Task CreateWithoutNameTest() + { + Collection collection = TestSample.GetNew(); + collection.Name = null; + await Assert.ThrowsAsync(() => _repository.Create(collection)); + } + + [Fact] + public async Task CreateWithExternalIdTest() + { + Collection collection = TestSample.GetNew(); + collection.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + new MetadataID + { + Provider = TestSample.GetNew(), + Link = "new-provider-link", + DataID = "new-id" + } + }; + await _repository.Create(collection); + + Collection retrieved = await _repository.Get(2); + await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs); + Assert.Equal(2, retrieved.ExternalIDs.Count); + KAssert.DeepEqual(collection.ExternalIDs.First(), retrieved.ExternalIDs.First()); + KAssert.DeepEqual(collection.ExternalIDs.Last(), retrieved.ExternalIDs.Last()); + } + + [Fact] + public async Task EditTest() + { + Collection value = await _repository.Get(TestSample.Get().Slug); + value.Name = "New Title"; + value.Images = new Dictionary + { + [Images.Poster] = "new-poster" + }; + await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + Collection retrieved = await database.Collections.FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + [Fact] + public async Task EditMetadataTest() + { + Collection value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + Collection retrieved = await database.Collections + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + [Fact] + public async Task AddMetadataTest() + { + Collection value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new List + { + new() + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + await _repository.Edit(value, false); + + { + await using DatabaseContext database = Repositories.Context.New(); + Collection retrieved = await database.Collections + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + value.ExternalIDs.Add(new MetadataID + { + Provider = TestSample.GetNew(), + Link = "link", + DataID = "id" + }); + await _repository.Edit(value, false); + + { + await using DatabaseContext database = Repositories.Context.New(); + Collection retrieved = await database.Collections + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + } + + [Theory] + [InlineData("test")] + [InlineData("super")] + [InlineData("title")] + [InlineData("TiTlE")] + [InlineData("SuPeR")] + public async Task SearchTest(string query) + { + Collection value = new() + { + Slug = "super-test", + Name = "This is a test title", + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } + } +} \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs similarity index 59% rename from Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index d9e0e9ff..8a477100 100644 --- a/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -1,6 +1,9 @@ +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; +using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; @@ -59,7 +62,8 @@ namespace Kyoo.Tests.Database episode = await _repository.Edit(new Episode { ID = 1, - SeasonNumber = 2 + SeasonNumber = 2, + ShowID = 1 }, false); Assert.Equal($"{TestSample.Get().Slug}-s2e1", episode.Slug); episode = await _repository.Get(1); @@ -74,7 +78,8 @@ namespace Kyoo.Tests.Database episode = await _repository.Edit(new Episode { ID = 1, - EpisodeNumber = 2 + EpisodeNumber = 2, + ShowID = 1 }, false); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(1); @@ -92,10 +97,6 @@ namespace Kyoo.Tests.Database }); Assert.Equal($"{TestSample.Get().Slug}-s2e4", episode.Slug); } - - - // TODO absolute numbering tests - [Fact] public void AbsoluteSlugTest() @@ -133,7 +134,8 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Edit(new Episode { ID = 2, - AbsoluteNumber = 56 + AbsoluteNumber = 56, + ShowID = 1 }, false); Assert.Equal($"{TestSample.Get().Slug}-56", episode.Slug); episode = await _repository.Get(2); @@ -148,7 +150,8 @@ namespace Kyoo.Tests.Database { ID = 2, SeasonNumber = 1, - EpisodeNumber = 2 + EpisodeNumber = 2, + ShowID = 1 }, false); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(2); @@ -188,5 +191,137 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Get(3); Assert.Equal("john-wick", episode.Slug); } + + [Fact] + public async Task CreateWithExternalIdTest() + { + Episode value = TestSample.GetNew(); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + new MetadataID + { + Provider = TestSample.GetNew(), + Link = "new-provider-link", + DataID = "new-id" + } + }; + await _repository.Create(value); + + Episode retrieved = await _repository.Get(2); + await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs); + Assert.Equal(2, retrieved.ExternalIDs.Count); + KAssert.DeepEqual(value.ExternalIDs.First(), retrieved.ExternalIDs.First()); + KAssert.DeepEqual(value.ExternalIDs.Last(), retrieved.ExternalIDs.Last()); + } + + [Fact] + public async Task EditTest() + { + Episode value = await _repository.Get(TestSample.Get().Slug); + value.Title = "New Title"; + value.Images = new Dictionary + { + [Images.Poster] = "new-poster" + }; + await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + Episode retrieved = await database.Episodes.FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + [Fact] + public async Task EditMetadataTest() + { + Episode value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + Episode retrieved = await database.Episodes + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + [Fact] + public async Task AddMetadataTest() + { + Episode value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new List + { + new() + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + await _repository.Edit(value, false); + + { + await using DatabaseContext database = Repositories.Context.New(); + Episode retrieved = await database.Episodes + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + value.ExternalIDs.Add(new MetadataID + { + Provider = TestSample.GetNew(), + Link = "link", + DataID = "id" + }); + await _repository.Edit(value, false); + + { + await using DatabaseContext database = Repositories.Context.New(); + Episode retrieved = await database.Episodes + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + } + + [Theory] + [InlineData("test")] + [InlineData("super")] + [InlineData("title")] + [InlineData("TiTlE")] + [InlineData("SuPeR")] + public async Task SearchTest(string query) + { + Episode value = new() + { + Title = "This is a test super title", + ShowID = 1, + AbsoluteNumber = 2 + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/GenreTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/GenreTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs b/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs similarity index 91% rename from Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs rename to tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs index c5639dbb..f3e031b2 100644 --- a/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs @@ -55,7 +55,7 @@ namespace Kyoo.Tests.Database [Fact] public async Task GetCollectionTests() { - LibraryItem expected = new(TestSample.Get()); + LibraryItem expected = new(TestSample.Get()); LibraryItem actual = await _repository.Get(-1); KAssert.DeepEqual(expected, actual); } @@ -79,9 +79,10 @@ namespace Kyoo.Tests.Database [Fact] public async Task GetDuplicatedSlugTests() { - await _repositories.LibraryManager.Create(new Collection() + await _repositories.LibraryManager.Create(new Collection { - Slug = TestSample.Get().Slug + Slug = TestSample.Get().Slug, + Name = "name" }); await Assert.ThrowsAsync(() => _repository.Get(TestSample.Get().Slug)); } diff --git a/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs new file mode 100644 index 00000000..79277f61 --- /dev/null +++ b/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.Controllers; +using Kyoo.Models; +using Microsoft.EntityFrameworkCore; +using Xunit; +using Xunit.Abstractions; + +namespace Kyoo.Tests.Database +{ + namespace SqLite + { + public class LibraryTests : ALibraryTests + { + public LibraryTests(ITestOutputHelper output) + : base(new RepositoryActivator(output)) { } + } + } + + namespace PostgreSQL + { + [Collection(nameof(Postgresql))] + public class LibraryTests : ALibraryTests + { + public LibraryTests(PostgresFixture postgres, ITestOutputHelper output) + : base(new RepositoryActivator(output, postgres)) { } + } + } + + public abstract class ALibraryTests : RepositoryTests + { + private readonly ILibraryRepository _repository; + + protected ALibraryTests(RepositoryActivator repositories) + : base(repositories) + { + _repository = Repositories.LibraryManager.LibraryRepository; + } + + [Fact] + public async Task CreateWithoutPathTest() + { + Library library = TestSample.GetNew(); + library.Paths = null; + await Assert.ThrowsAsync(() => _repository.Create(library)); + } + + [Fact] + public async Task CreateWithEmptySlugTest() + { + Library library = TestSample.GetNew(); + library.Slug = ""; + await Assert.ThrowsAsync(() => _repository.Create(library)); + } + + [Fact] + public async Task CreateWithNumberSlugTest() + { + Library library = TestSample.GetNew(); + library.Slug = "2"; + Library ret = await _repository.Create(library); + Assert.Equal("2!", ret.Slug); + } + + [Fact] + public async Task CreateWithoutNameTest() + { + Library library = TestSample.GetNew(); + library.Name = null; + await Assert.ThrowsAsync(() => _repository.Create(library)); + } + + [Fact] + public async Task CreateWithProvider() + { + Library library = TestSample.GetNew(); + library.Providers = new[] { TestSample.Get() }; + await _repository.Create(library); + Library retrieved = await _repository.Get(2); + await Repositories.LibraryManager.Load(retrieved, x => x.Providers); + Assert.Equal(1, retrieved.Providers.Count); + Assert.Equal(TestSample.Get().Slug, retrieved.Providers.First().Slug); + } + + [Fact] + public async Task EditTest() + { + Library value = await _repository.Get(TestSample.Get().Slug); + value.Paths = new [] {"/super", "/test"}; + value.Name = "New Title"; + Library edited = await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + Library show = await database.Libraries.FirstAsync(); + + KAssert.DeepEqual(show, edited); + } + + [Fact] + public async Task EditProvidersTest() + { + Library value = await _repository.Get(TestSample.Get().Slug); + value.Providers = new[] + { + TestSample.GetNew() + }; + Library edited = await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + Library show = await database.Libraries + .Include(x => x.Providers) + .FirstAsync(); + + show.Providers.ForEach(x => x.Libraries = null); + edited.Providers.ForEach(x => x.Libraries = null); + KAssert.DeepEqual(show, edited); + } + + [Fact] + public async Task AddProvidersTest() + { + Library value = await _repository.Get(TestSample.Get().Slug); + await Repositories.LibraryManager.Load(value, x => x.Providers); + value.Providers.Add(TestSample.GetNew()); + Library edited = await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + Library show = await database.Libraries + .Include(x => x.Providers) + .FirstAsync(); + + show.Providers.ForEach(x => x.Libraries = null); + edited.Providers.ForEach(x => x.Libraries = null); + KAssert.DeepEqual(show, edited); + } + + [Theory] + [InlineData("test")] + [InlineData("super")] + [InlineData("title")] + [InlineData("TiTlE")] + [InlineData("SuPeR")] + public async Task SearchTest(string query) + { + Library value = new() + { + Slug = "super-test", + Name = "This is a test title", + Paths = new [] {"path"} + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } + } +} \ No newline at end of file diff --git a/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs new file mode 100644 index 00000000..3de88edf --- /dev/null +++ b/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs @@ -0,0 +1,170 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.Controllers; +using Kyoo.Models; +using Microsoft.EntityFrameworkCore; +using Xunit; +using Xunit.Abstractions; + +namespace Kyoo.Tests.Database +{ + namespace SqLite + { + public class PeopleTests : APeopleTests + { + public PeopleTests(ITestOutputHelper output) + : base(new RepositoryActivator(output)) { } + } + } + + namespace PostgreSQL + { + [Collection(nameof(Postgresql))] + public class PeopleTests : APeopleTests + { + public PeopleTests(PostgresFixture postgres, ITestOutputHelper output) + : base(new RepositoryActivator(output, postgres)) { } + } + } + + public abstract class APeopleTests : RepositoryTests + { + private readonly IPeopleRepository _repository; + + protected APeopleTests(RepositoryActivator repositories) + : base(repositories) + { + _repository = Repositories.LibraryManager.PeopleRepository; + } + + [Fact] + public async Task CreateWithExternalIdTest() + { + People value = TestSample.GetNew(); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + new MetadataID + { + Provider = TestSample.GetNew(), + Link = "new-provider-link", + DataID = "new-id" + } + }; + await _repository.Create(value); + + People retrieved = await _repository.Get(2); + await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs); + Assert.Equal(2, retrieved.ExternalIDs.Count); + KAssert.DeepEqual(value.ExternalIDs.First(), retrieved.ExternalIDs.First()); + KAssert.DeepEqual(value.ExternalIDs.Last(), retrieved.ExternalIDs.Last()); + } + + [Fact] + public async Task EditTest() + { + People value = await _repository.Get(TestSample.Get().Slug); + value.Name = "New Name"; + value.Images = new Dictionary + { + [Images.Poster] = "new-poster" + }; + await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + People retrieved = await database.People.FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + [Fact] + public async Task EditMetadataTest() + { + People value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + People retrieved = await database.People + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + [Fact] + public async Task AddMetadataTest() + { + People value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new List + { + new() + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + await _repository.Edit(value, false); + + { + await using DatabaseContext database = Repositories.Context.New(); + People retrieved = await database.People + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + value.ExternalIDs.Add(new MetadataID + { + Provider = TestSample.GetNew(), + Link = "link", + DataID = "id" + }); + await _repository.Edit(value, false); + + { + await using DatabaseContext database = Repositories.Context.New(); + People retrieved = await database.People + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + } + + [Theory] + [InlineData("Me")] + [InlineData("me")] + [InlineData("na")] + public async Task SearchTest(string query) + { + People value = new() + { + Slug = "slug", + Name = "name", + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } + } +} \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/ProviderTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/SanityTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/SanityTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/SanityTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/SanityTests.cs index 933bbf82..a071ee2b 100644 --- a/Kyoo.Tests/Database/SpecificTests/SanityTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/SanityTests.cs @@ -16,13 +16,6 @@ namespace Kyoo.Tests.Database _repositories = new RepositoryActivator(output); } - [Fact] - [SuppressMessage("ReSharper", "EqualExpressionComparison")] - public void SampleTest() - { - Assert.False(ReferenceEquals(TestSample.Get(), TestSample.Get())); - } - public void Dispose() { _repositories.Dispose(); @@ -33,5 +26,12 @@ namespace Kyoo.Tests.Database { return _repositories.DisposeAsync(); } + + [Fact] + [SuppressMessage("ReSharper", "EqualExpressionComparison")] + public void SampleTest() + { + Assert.False(ReferenceEquals(TestSample.Get(), TestSample.Get())); + } } } \ No newline at end of file diff --git a/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs new file mode 100644 index 00000000..ae56415c --- /dev/null +++ b/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -0,0 +1,214 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.Controllers; +using Kyoo.Models; +using Microsoft.EntityFrameworkCore; +using Xunit; +using Xunit.Abstractions; + +namespace Kyoo.Tests.Database +{ + namespace SqLite + { + public class SeasonTests : ASeasonTests + { + public SeasonTests(ITestOutputHelper output) + : base(new RepositoryActivator(output)) { } + } + } + + + namespace PostgreSQL + { + [Collection(nameof(Postgresql))] + public class SeasonTests : ASeasonTests + { + public SeasonTests(PostgresFixture postgres, ITestOutputHelper output) + : base(new RepositoryActivator(output, postgres)) { } + } + } + + public abstract class ASeasonTests : RepositoryTests + { + private readonly ISeasonRepository _repository; + + protected ASeasonTests(RepositoryActivator repositories) + : base(repositories) + { + _repository = Repositories.LibraryManager.SeasonRepository; + } + + [Fact] + public async Task SlugEditTest() + { + Season season = await _repository.Get(1); + Assert.Equal("anohana-s1", season.Slug); + Show show = new() + { + ID = season.ShowID, + Slug = "new-slug" + }; + await Repositories.LibraryManager.ShowRepository.Edit(show, false); + season = await _repository.Get(1); + Assert.Equal("new-slug-s1", season.Slug); + } + + [Fact] + public async Task SeasonNumberEditTest() + { + Season season = await _repository.Get(1); + Assert.Equal("anohana-s1", season.Slug); + await _repository.Edit(new Season + { + ID = 1, + SeasonNumber = 2, + ShowID = 1 + }, false); + season = await _repository.Get(1); + Assert.Equal("anohana-s2", season.Slug); + } + + [Fact] + public async Task SeasonCreationSlugTest() + { + Season season = await _repository.Create(new Season + { + ShowID = TestSample.Get().ID, + SeasonNumber = 2 + }); + Assert.Equal($"{TestSample.Get().Slug}-s2", season.Slug); + } + + [Fact] + public async Task CreateWithExternalIdTest() + { + Season season = TestSample.GetNew(); + season.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + new MetadataID + { + Provider = TestSample.GetNew(), + Link = "new-provider-link", + DataID = "new-id" + } + }; + await _repository.Create(season); + + Season retrieved = await _repository.Get(2); + await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs); + Assert.Equal(2, retrieved.ExternalIDs.Count); + KAssert.DeepEqual(season.ExternalIDs.First(), retrieved.ExternalIDs.First()); + KAssert.DeepEqual(season.ExternalIDs.Last(), retrieved.ExternalIDs.Last()); + } + + [Fact] + public async Task EditTest() + { + Season value = await _repository.Get(TestSample.Get().Slug); + value.Title = "New Title"; + value.Images = new Dictionary + { + [Images.Poster] = "new-poster" + }; + await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + Season retrieved = await database.Seasons.FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + [Fact] + public async Task EditMetadataTest() + { + Season value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + await _repository.Edit(value, false); + + await using DatabaseContext database = Repositories.Context.New(); + Season retrieved = await database.Seasons + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + [Fact] + public async Task AddMetadataTest() + { + Season value = await _repository.Get(TestSample.Get().Slug); + value.ExternalIDs = new List + { + new() + { + Provider = TestSample.Get(), + Link = "link", + DataID = "id" + }, + }; + await _repository.Edit(value, false); + + { + await using DatabaseContext database = Repositories.Context.New(); + Season retrieved = await database.Seasons + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + + value.ExternalIDs.Add(new MetadataID + { + Provider = TestSample.GetNew(), + Link = "link", + DataID = "id" + }); + await _repository.Edit(value, false); + + { + await using DatabaseContext database = Repositories.Context.New(); + Season retrieved = await database.Seasons + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(); + + KAssert.DeepEqual(value, retrieved); + } + } + + [Theory] + [InlineData("test")] + [InlineData("super")] + [InlineData("title")] + [InlineData("TiTlE")] + [InlineData("SuPeR")] + public async Task SearchTest(string query) + { + Season value = new() + { + Title = "This is a test super title", + ShowID = 1 + }; + await _repository.Create(value); + ICollection ret = await _repository.Search(query); + KAssert.DeepEqual(value, ret.First()); + } + } +} \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs similarity index 69% rename from Kyoo.Tests/Database/SpecificTests/ShowTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index 63207710..d3111f75 100644 --- a/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using FluentAssertions; using Kyoo.Controllers; using Kyoo.Models; using Microsoft.EntityFrameworkCore; @@ -73,6 +74,26 @@ namespace Kyoo.Tests.Database Assert.Equal(value.Genres.Select(x => new{x.Slug, x.Name}), show.Genres.Select(x => new{x.Slug, x.Name})); } + [Fact] + public async Task AddGenreTest() + { + Show value = await _repository.Get(TestSample.Get().Slug); + await Repositories.LibraryManager.Load(value, x => x.Genres); + value.Genres.Add(new Genre("test")); + Show edited = await _repository.Edit(value, false); + + Assert.Equal(value.Slug, edited.Slug); + Assert.Equal(value.Genres.Select(x => new{x.Slug, x.Name}), edited.Genres.Select(x => new{x.Slug, x.Name})); + + await using DatabaseContext database = Repositories.Context.New(); + Show show = await database.Shows + .Include(x => x.Genres) + .FirstAsync(); + + Assert.Equal(value.Slug, show.Slug); + Assert.Equal(value.Genres.Select(x => new{x.Slug, x.Name}), show.Genres.Select(x => new{x.Slug, x.Name})); + } + [Fact] public async Task EditStudioTest() { @@ -85,11 +106,11 @@ namespace Kyoo.Tests.Database await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows - .Include(x => x.Genres) + .Include(x => x.Studio) .FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal("studio", edited.Studio.Slug); + Assert.Equal("studio", show.Studio.Slug); } [Fact] @@ -106,7 +127,7 @@ namespace Kyoo.Tests.Database Show show = await database.Shows.FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal(value.Aliases, edited.Aliases); + Assert.Equal(value.Aliases, show.Aliases); } [Fact] @@ -135,12 +156,13 @@ namespace Kyoo.Tests.Database await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows .Include(x => x.People) + .ThenInclude(x => x.People) .FirstAsync(); Assert.Equal(value.Slug, show.Slug); Assert.Equal( value.People.Select(x => new{x.Role, x.Slug, x.People.Name}), - edited.People.Select(x => new{x.Role, x.Slug, x.People.Name})); + show.People.Select(x => new{x.Role, x.Slug, x.People.Name})); } [Fact] @@ -149,10 +171,9 @@ namespace Kyoo.Tests.Database Show value = await _repository.Get(TestSample.Get().Slug); value.ExternalIDs = new[] { - new MetadataID() + new MetadataID { - First = value, - Second = new Provider("test", "test.png"), + Provider = new Provider("test", "test.png"), DataID = "1234" } }; @@ -160,19 +181,19 @@ namespace Kyoo.Tests.Database Assert.Equal(value.Slug, edited.Slug); Assert.Equal( - value.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}), - edited.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug})); + value.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}), + edited.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug})); await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows .Include(x => x.ExternalIDs) - .ThenInclude(x => x.Second) + .ThenInclude(x => x.Provider) .FirstAsync(); Assert.Equal(value.Slug, show.Slug); Assert.Equal( - value.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}), - show.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug})); + value.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}), + show.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug})); } [Fact] @@ -209,10 +230,9 @@ namespace Kyoo.Tests.Database expected.Slug = "created-relation-test"; expected.ExternalIDs = new[] { - new MetadataID + new MetadataID { - First = expected, - Second = new Provider("provider", "provider.png"), + Provider = new Provider("provider", "provider.png"), DataID = "ID" } }; @@ -237,6 +257,58 @@ namespace Kyoo.Tests.Database expected.Studio = new Studio("studio"); Show created = await _repository.Create(expected); KAssert.DeepEqual(expected, created); + + await using DatabaseContext context = Repositories.Context.New(); + Show retrieved = await context.Shows + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .Include(x => x.Genres) + .Include(x => x.People) + .ThenInclude(x => x.People) + .Include(x => x.Studio) + .FirstAsync(x => x.ID == created.ID); + retrieved.People.ForEach(x => + { + x.Show = null; + x.People.Roles = null; + }); + retrieved.Studio.Shows = null; + retrieved.Genres.ForEach(x => x.Shows = null); + + expected.Genres.ForEach(x => x.Shows = null); + expected.People.ForEach(x => + { + x.Show = null; + x.People.Roles = null; + }); + + retrieved.Should().BeEquivalentTo(expected); + } + + [Fact] + public async Task CreateWithExternalID() + { + Show expected = TestSample.Get(); + expected.ID = 0; + expected.Slug = "created-relation-test"; + expected.ExternalIDs = new[] + { + new MetadataID + { + Provider = TestSample.Get(), + DataID = "ID" + } + }; + Show created = await _repository.Create(expected); + KAssert.DeepEqual(expected, created); + await using DatabaseContext context = Repositories.Context.New(); + Show retrieved = await context.Shows + .Include(x => x.ExternalIDs) + .ThenInclude(x => x.Provider) + .FirstAsync(x => x.ID == created.ID); + KAssert.DeepEqual(expected, retrieved); + Assert.Equal(1, retrieved.ExternalIDs.Count); + Assert.Equal("ID", retrieved.ExternalIDs.First().DataID); } [Fact] @@ -288,5 +360,18 @@ namespace Kyoo.Tests.Database Assert.Equal(0, await Repositories.LibraryManager.SeasonRepository.GetCount()); Assert.Equal(0, await Repositories.LibraryManager.EpisodeRepository.GetCount()); } + + [Fact] + public async Task AddShowLinkTest() + { + await Repositories.LibraryManager.Create(TestSample.GetNew()); + await _repository.AddShowLink(1, 2, null); + + await using DatabaseContext context = Repositories.Context.New(); + Show show = context.Shows + .Include(x => x.Libraries) + .First(x => x.ID == 1); + Assert.Contains(2, show.Libraries.Select(x => x.ID)); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Database/SpecificTests/StudioTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/StudioTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/StudioTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/StudioTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/TrackTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/TrackTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/TrackTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/TrackTests.cs diff --git a/Kyoo.Tests/Database/SpecificTests/UserTests.cs b/tests/Kyoo.Tests/Database/SpecificTests/UserTests.cs similarity index 100% rename from Kyoo.Tests/Database/SpecificTests/UserTests.cs rename to tests/Kyoo.Tests/Database/SpecificTests/UserTests.cs diff --git a/Kyoo.Tests/Database/TestContext.cs b/tests/Kyoo.Tests/Database/TestContext.cs similarity index 100% rename from Kyoo.Tests/Database/TestContext.cs rename to tests/Kyoo.Tests/Database/TestContext.cs diff --git a/Kyoo.Tests/Database/TestSample.cs b/tests/Kyoo.Tests/Database/TestSample.cs similarity index 62% rename from Kyoo.Tests/Database/TestSample.cs rename to tests/Kyoo.Tests/Database/TestSample.cs index e0ff955f..6f9a69ff 100644 --- a/Kyoo.Tests/Database/TestSample.cs +++ b/tests/Kyoo.Tests/Database/TestSample.cs @@ -17,6 +17,107 @@ namespace Kyoo.Tests Name = "New Library", Paths = new [] {"/a/random/path"} } + }, + { + typeof(Collection), + () => new Collection + { + ID = 2, + Slug = "new-collection", + Name = "New Collection", + Overview = "A collection created by new sample", + Images = new Dictionary + { + [Images.Thumbnail] = "thumbnail" + } + } + }, + { + typeof(Show), + () => new Show + { + ID = 2, + Slug = "new-show", + Title = "New Show", + Overview = "overview", + Status = Status.Planned, + StartAir = new DateTime(2011, 1, 1), + EndAir = new DateTime(2011, 1, 1), + Images = new Dictionary + { + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" + }, + IsMovie = false, + Studio = null + } + }, + { + typeof(Season), + () => new Season + { + ID = 2, + ShowID = 1, + ShowSlug = Get().Slug, + Title = "New season", + Overview = "New overview", + EndDate = new DateTime(2000, 10, 10), + SeasonNumber = 2, + StartDate = new DateTime(2010, 10, 10), + Images = new Dictionary + { + [Images.Logo] = "logo" + } + } + }, + { + typeof(Episode), + () => new Episode + { + ID = 2, + ShowID = 1, + ShowSlug = Get().Slug, + SeasonID = 1, + SeasonNumber = Get().SeasonNumber, + EpisodeNumber = 3, + AbsoluteNumber = 4, + Path = "/episode-path", + Title = "New Episode Title", + ReleaseDate = new DateTime(2000, 10, 10), + Overview = "new episode overview", + Images = new Dictionary + { + [Images.Logo] = "new episode logo" + } + } + }, + { + typeof(Provider), + () => new Provider + { + ID = 2, + Slug = "new-provider", + Name = "Provider NewSample", + Images = new Dictionary + { + [Images.Logo] = "logo" + } + } + }, + { + typeof(People), + () => new People + { + ID = 2, + Slug = "new-person-name", + Name = "New person name", + Images = new Dictionary + { + [Images.Logo] = "Old Logo", + [Images.Poster] = "Old poster" + } + } } }; @@ -41,7 +142,10 @@ namespace Kyoo.Tests Slug = "collection", Name = "Collection", Overview = "A nice collection for tests", - Poster = "Poster" + Images = new Dictionary + { + [Images.Poster] = "Poster" + } } }, { @@ -61,12 +165,15 @@ namespace Kyoo.Tests "In time, however, these childhood friends drifted apart, and when they became high " + "school students, they had long ceased to think of each other as friends.", Status = Status.Finished, - TrailerUrl = null, + StudioID = 1, StartAir = new DateTime(2011, 1, 1), EndAir = new DateTime(2011, 1, 1), - Poster = "poster", - Logo = "logo", - Backdrop = "backdrop", + Images = new Dictionary + { + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" + }, IsMovie = false, Studio = null } @@ -83,7 +190,12 @@ namespace Kyoo.Tests Overview = "The first season", StartDate = new DateTime(2020, 06, 05), EndDate = new DateTime(2020, 07, 05), - Poster = "poster" + Images = new Dictionary + { + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" + }, } }, { @@ -98,7 +210,12 @@ namespace Kyoo.Tests EpisodeNumber = 1, AbsoluteNumber = 1, Path = "/home/kyoo/anohana-s1e1", - Thumb = "thumbnail", + Images = new Dictionary + { + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" + }, Title = "Episode 1", Overview = "Summary of the first episode", ReleaseDate = new DateTime(2020, 06, 05) @@ -129,7 +246,12 @@ namespace Kyoo.Tests ID = 1, Slug = "the-actor", Name = "The Actor", - Poster = "NicePoster" + Images = new Dictionary + { + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" + }, } }, { @@ -138,7 +260,7 @@ namespace Kyoo.Tests { ID = 1, Slug = "hyper-studio", - Name = "Hyper studio" + Name = "Hyper studio", } }, { @@ -157,8 +279,12 @@ namespace Kyoo.Tests ID = 1, Slug = "tvdb", Name = "The TVDB", - Logo = "path/tvdb.svg", - LogoExtension = "svg" + Images = new Dictionary + { + [Images.Poster] = "Poster", + [Images.Logo] = "path/tvdb.svg", + [Images.Thumbnail] = "Thumbnail" + } } }, { @@ -193,6 +319,7 @@ namespace Kyoo.Tests Show show = Get(); show.ID = 0; + show.StudioID = 0; context.Shows.Add(show); Season season = Get(); @@ -257,7 +384,12 @@ namespace Kyoo.Tests EpisodeNumber = null, AbsoluteNumber = 3, Path = "/home/kyoo/anohana-3", - Thumb = "thumbnail", + Images = new Dictionary + { + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" + }, Title = "Episode 3", Overview = "Summary of the third absolute episode", ReleaseDate = new DateTime(2020, 06, 05) @@ -272,7 +404,12 @@ namespace Kyoo.Tests ShowSlug = "anohana", ShowID = 1, Path = "/home/kyoo/john-wick", - Thumb = "thumb", + Images = new Dictionary + { + [Images.Poster] = "Poster", + [Images.Logo] = "Logo", + [Images.Thumbnail] = "Thumbnail" + }, Title = "John wick", Overview = "A movie episode test", ReleaseDate = new DateTime(1595, 05, 12) diff --git a/Kyoo.Tests/Identifier/IdentifierTests.cs b/tests/Kyoo.Tests/Identifier/IdentifierTests.cs similarity index 100% rename from Kyoo.Tests/Identifier/IdentifierTests.cs rename to tests/Kyoo.Tests/Identifier/IdentifierTests.cs diff --git a/Kyoo.Tests/Identifier/ProviderTests.cs b/tests/Kyoo.Tests/Identifier/ProviderTests.cs similarity index 100% rename from Kyoo.Tests/Identifier/ProviderTests.cs rename to tests/Kyoo.Tests/Identifier/ProviderTests.cs diff --git a/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs b/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs similarity index 90% rename from Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs rename to tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs index 35e389b6..a761ab9e 100644 --- a/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs +++ b/tests/Kyoo.Tests/Identifier/Tvdb/ConvertorTests.cs @@ -32,7 +32,7 @@ namespace Kyoo.Tests.Identifier.Tvdb Assert.Equal("Aliases", show.Aliases[0]); Assert.Equal("overview", show.Overview); Assert.Equal(new DateTime(2021, 7, 23), show.StartAir); - Assert.Equal("https://www.thetvdb.com/poster", show.Poster); + Assert.Equal("https://www.thetvdb.com/poster", show.Images[Images.Poster]); Assert.Single(show.ExternalIDs); Assert.Equal("5", show.ExternalIDs.First().DataID); Assert.Equal(provider, show.ExternalIDs.First().Provider); @@ -63,7 +63,7 @@ namespace Kyoo.Tests.Identifier.Tvdb Assert.Equal("Aliases", show.Aliases[0]); Assert.Equal("overview", show.Overview); Assert.Null(show.StartAir); - Assert.Equal("https://www.thetvdb.com/poster", show.Poster); + Assert.Equal("https://www.thetvdb.com/poster", show.Images[Images.Poster]); Assert.Single(show.ExternalIDs); Assert.Equal("5", show.ExternalIDs.First().DataID); Assert.Equal(provider, show.ExternalIDs.First().Provider); @@ -100,8 +100,8 @@ namespace Kyoo.Tests.Identifier.Tvdb Assert.Equal("Aliases", show.Aliases[0]); Assert.Equal("overview", show.Overview); Assert.Equal(new DateTime(2021, 7, 23), show.StartAir); - Assert.Equal("https://www.thetvdb.com/banners/poster", show.Poster); - Assert.Equal("https://www.thetvdb.com/banners/fanart", show.Backdrop); + Assert.Equal("https://www.thetvdb.com/banners/poster", show.Images[Images.Poster]); + Assert.Equal("https://www.thetvdb.com/banners/fanart", show.Images[Images.Thumbnail]); Assert.Single(show.ExternalIDs); Assert.Equal("5", show.ExternalIDs.First().DataID); Assert.Equal(provider, show.ExternalIDs.First().Provider); @@ -124,13 +124,12 @@ namespace Kyoo.Tests.Identifier.Tvdb Name = "Name", Role = "role" }; - Provider provider = TestSample.Get(); - PeopleRole people = actor.ToPeopleRole(provider); + PeopleRole people = actor.ToPeopleRole(); Assert.Equal("name", people.Slug); Assert.Equal("Name", people.People.Name); Assert.Equal("role", people.Role); - Assert.Equal("https://www.thetvdb.com/banners/image", people.People.Poster); + Assert.Equal("https://www.thetvdb.com/banners/image", people.People.Images[Images.Poster]); } [Fact] @@ -154,7 +153,7 @@ namespace Kyoo.Tests.Identifier.Tvdb Assert.Equal(3, episode.EpisodeNumber); Assert.Equal(23, episode.AbsoluteNumber); Assert.Equal("overview", episode.Overview); - Assert.Equal("https://www.thetvdb.com/banners/thumb", episode.Thumb); + Assert.Equal("https://www.thetvdb.com/banners/thumb", episode.Images[Images.Thumbnail]); } } } \ No newline at end of file diff --git a/Kyoo.Tests/KAssert.cs b/tests/Kyoo.Tests/KAssert.cs similarity index 82% rename from Kyoo.Tests/KAssert.cs rename to tests/Kyoo.Tests/KAssert.cs index 80209c98..e4c2e28f 100644 --- a/Kyoo.Tests/KAssert.cs +++ b/tests/Kyoo.Tests/KAssert.cs @@ -1,6 +1,5 @@ -using System.Reflection; +using FluentAssertions; using JetBrains.Annotations; -using Xunit; using Xunit.Sdk; namespace Kyoo.Tests @@ -19,8 +18,7 @@ namespace Kyoo.Tests [AssertionMethod] public static void DeepEqual(T expected, T value) { - foreach (PropertyInfo property in typeof(T).GetProperties(BindingFlags.Instance)) - Assert.Equal(property.GetValue(expected), property.GetValue(value)); + value.Should().BeEquivalentTo(expected); } /// diff --git a/Kyoo.Tests/Kyoo.Tests.csproj b/tests/Kyoo.Tests/Kyoo.Tests.csproj similarity index 73% rename from Kyoo.Tests/Kyoo.Tests.csproj rename to tests/Kyoo.Tests/Kyoo.Tests.csproj index 120f8ba7..f174bc55 100644 --- a/Kyoo.Tests/Kyoo.Tests.csproj +++ b/tests/Kyoo.Tests/Kyoo.Tests.csproj @@ -10,11 +10,12 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all + @@ -24,17 +25,17 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - + + + + diff --git a/Kyoo.Tests/Utility/EnumerableTests.cs b/tests/Kyoo.Tests/Utility/EnumerableTests.cs similarity index 100% rename from Kyoo.Tests/Utility/EnumerableTests.cs rename to tests/Kyoo.Tests/Utility/EnumerableTests.cs diff --git a/tests/Kyoo.Tests/Utility/MergerTests.cs b/tests/Kyoo.Tests/Utility/MergerTests.cs new file mode 100644 index 00000000..6aed3eb2 --- /dev/null +++ b/tests/Kyoo.Tests/Utility/MergerTests.cs @@ -0,0 +1,459 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using JetBrains.Annotations; +using Kyoo.Models; +using Kyoo.Models.Attributes; +using Xunit; + +namespace Kyoo.Tests.Utility +{ + public class MergerTests + { + [Fact] + public void NullifyTest() + { + Genre genre = new("test") + { + ID = 5 + }; + Merger.Nullify(genre); + Assert.Equal(0, genre.ID); + Assert.Null(genre.Name); + Assert.Null(genre.Slug); + } + + [Fact] + public void MergeTest() + { + Genre genre = new() + { + ID = 5 + }; + Genre genre2 = new() + { + Name = "test" + }; + Genre ret = Merger.Merge(genre, genre2); + Assert.True(ReferenceEquals(genre, ret)); + Assert.Equal(5, ret.ID); + Assert.Equal("test", genre.Name); + Assert.Null(genre.Slug); + } + + [Fact] + [SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")] + public void MergeNullTests() + { + Genre genre = new() + { + ID = 5 + }; + Assert.True(ReferenceEquals(genre, Merger.Merge(genre, null))); + Assert.True(ReferenceEquals(genre, Merger.Merge(null, genre))); + Assert.Null(Merger.Merge(null, null)); + } + + private class TestIOnMerge : IOnMerge + { + public void OnMerge(object other) + { + Exception exception = new(); + exception.Data[0] = other; + throw exception; + } + } + + [Fact] + public void OnMergeTest() + { + TestIOnMerge test = new(); + TestIOnMerge test2 = new(); + Assert.Throws(() => Merger.Merge(test, test2)); + try + { + Merger.Merge(test, test2); + } + catch (Exception ex) + { + Assert.True(ReferenceEquals(test2, ex.Data[0])); + } + } + + private class Test + { + public int ID { get; set; } + + public int[] Numbers { get; set; } + } + + [Fact] + public void GlobalMergeListTest() + { + Test test = new() + { + ID = 5, + Numbers = new [] { 1 } + }; + Test test2 = new() + { + Numbers = new [] { 3 } + }; + Test ret = Merger.Merge(test, test2); + Assert.True(ReferenceEquals(test, ret)); + Assert.Equal(5, ret.ID); + + Assert.Equal(2, ret.Numbers.Length); + Assert.Equal(1, ret.Numbers[0]); + Assert.Equal(3, ret.Numbers[1]); + } + + [Fact] + public void GlobalMergeListDuplicatesTest() + { + Test test = new() + { + ID = 5, + Numbers = new [] { 1 } + }; + Test test2 = new() + { + Numbers = new [] + { + 1, + 3, + 3 + } + }; + Test ret = Merger.Merge(test, test2); + Assert.True(ReferenceEquals(test, ret)); + Assert.Equal(5, ret.ID); + + Assert.Equal(4, ret.Numbers.Length); + Assert.Equal(1, ret.Numbers[0]); + Assert.Equal(1, ret.Numbers[1]); + Assert.Equal(3, ret.Numbers[2]); + Assert.Equal(3, ret.Numbers[3]); + } + + private class MergeDictionaryTest + { + public int ID { get; set; } + + public Dictionary Dictionary { get; set; } + } + + [Fact] + public void GlobalMergeDictionariesTest() + { + MergeDictionaryTest test = new() + { + ID = 5, + Dictionary = new Dictionary + { + [2] = "two" + } + }; + MergeDictionaryTest test2 = new() + { + Dictionary = new Dictionary + { + [3] = "third" + } + }; + MergeDictionaryTest ret = Merger.Merge(test, test2); + Assert.True(ReferenceEquals(test, ret)); + Assert.Equal(5, ret.ID); + + Assert.Equal(2, ret.Dictionary.Count); + Assert.Equal("two", ret.Dictionary[2]); + Assert.Equal("third", ret.Dictionary[3]); + } + + [Fact] + public void GlobalMergeDictionariesDuplicatesTest() + { + MergeDictionaryTest test = new() + { + ID = 5, + Dictionary = new Dictionary + { + [2] = "two" + } + }; + MergeDictionaryTest test2 = new() + { + Dictionary = new Dictionary + { + [2] = "nope", + [3] = "third" + } + }; + MergeDictionaryTest ret = Merger.Merge(test, test2); + Assert.True(ReferenceEquals(test, ret)); + Assert.Equal(5, ret.ID); + + Assert.Equal(2, ret.Dictionary.Count); + Assert.Equal("two", ret.Dictionary[2]); + Assert.Equal("third", ret.Dictionary[3]); + } + + [Fact] + public void GlobalMergeListDuplicatesResourcesTest() + { + Show test = new() + { + ID = 5, + Genres = new [] { new Genre("test") } + }; + Show test2 = new() + { + Genres = new [] + { + new Genre("test"), + new Genre("test2") + } + }; + Show ret = Merger.Merge(test, test2); + Assert.True(ReferenceEquals(test, ret)); + Assert.Equal(5, ret.ID); + + Assert.Equal(2, ret.Genres.Count); + Assert.Equal("test", ret.Genres.ToArray()[0].Slug); + Assert.Equal("test2", ret.Genres.ToArray()[1].Slug); + } + + [Fact] + public void MergeListTest() + { + int[] first = { 1 }; + int[] second = { + 3, + 3 + }; + int[] ret = Merger.MergeLists(first, second); + + Assert.Equal(3, ret.Length); + Assert.Equal(1, ret[0]); + Assert.Equal(3, ret[1]); + Assert.Equal(3, ret[2]); + } + + [Fact] + public void MergeListDuplicateTest() + { + int[] first = { 1 }; + int[] second = { + 1, + 3, + 3 + }; + int[] ret = Merger.MergeLists(first, second); + + Assert.Equal(4, ret.Length); + Assert.Equal(1, ret[0]); + Assert.Equal(1, ret[1]); + Assert.Equal(3, ret[2]); + Assert.Equal(3, ret[3]); + } + + [Fact] + public void MergeListDuplicateCustomEqualityTest() + { + int[] first = { 1 }; + int[] second = { + 3, + 2 + }; + int[] ret = Merger.MergeLists(first, second, (x, y) => x % 2 == y % 2); + + Assert.Equal(2, ret.Length); + Assert.Equal(1, ret[0]); + Assert.Equal(2, ret[1]); + } + + [Fact] + public void MergeDictionariesTest() + { + Dictionary first = new() + { + [1] = "test", + [5] = "value" + }; + Dictionary second = new() + { + [3] = "third", + }; + IDictionary ret = Merger.MergeDictionaries(first, second); + + Assert.Equal(3, ret.Count); + Assert.Equal("test", ret[1]); + Assert.Equal("value", ret[5]); + Assert.Equal("third", ret[3]); + } + + [Fact] + public void MergeDictionariesDuplicateTest() + { + Dictionary first = new() + { + [1] = "test", + [5] = "value" + }; + Dictionary second = new() + { + [3] = "third", + [5] = "new-value", + }; + IDictionary ret = Merger.MergeDictionaries(first, second); + + Assert.Equal(3, ret.Count); + Assert.Equal("test", ret[1]); + Assert.Equal("value", ret[5]); + Assert.Equal("third", ret[3]); + } + + [Fact] + public void CompleteTest() + { + Genre genre = new() + { + ID = 5, + Name = "merged" + }; + Genre genre2 = new() + { + Name = "test" + }; + Genre ret = Merger.Complete(genre, genre2); + Assert.True(ReferenceEquals(genre, ret)); + Assert.Equal(5, ret.ID); + Assert.Equal("test", genre.Name); + Assert.Null(genre.Slug); + } + + [Fact] + public void CompleteDictionaryTest() + { + Collection collection = new() + { + ID = 5, + Name = "merged", + Images = new Dictionary + { + [Images.Logo] = "logo", + [Images.Poster] = "poster" + } + + }; + Collection collection2 = new() + { + Name = "test", + Images = new Dictionary + { + [Images.Poster] = "new-poster", + [Images.Thumbnail] = "thumbnails" + } + }; + Collection ret = Merger.Complete(collection, collection2); + Assert.True(ReferenceEquals(collection, ret)); + Assert.Equal(5, ret.ID); + Assert.Equal("test", ret.Name); + Assert.Null(ret.Slug); + Assert.Equal(3, ret.Images.Count); + Assert.Equal("new-poster", ret.Images[Images.Poster]); + Assert.Equal("thumbnails", ret.Images[Images.Thumbnail]); + Assert.Equal("logo", ret.Images[Images.Logo]); + } + + [Fact] + public void CompleteDictionaryOutParam() + { + Dictionary first = new() + { + [Images.Logo] = "logo", + [Images.Poster] = "poster" + }; + Dictionary second = new() + { + [Images.Poster] = "new-poster", + [Images.Thumbnail] = "thumbnails" + }; + IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); + Assert.True(changed); + Assert.Equal(3, ret.Count); + Assert.Equal("new-poster", ret[Images.Poster]); + Assert.Equal("thumbnails", ret[Images.Thumbnail]); + Assert.Equal("logo", ret[Images.Logo]); + } + + [Fact] + public void CompleteDictionaryEqualTest() + { + Dictionary first = new() + { + [Images.Poster] = "poster" + }; + Dictionary second = new() + { + [Images.Poster] = "new-poster", + }; + IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); + Assert.True(changed); + Assert.Equal(1, ret.Count); + Assert.Equal("new-poster", ret[Images.Poster]); + } + + private class TestMergeSetter + { + public Dictionary Backing; + + [UsedImplicitly] public Dictionary Dictionary + { + get => Backing; + set + { + Backing = value; + KAssert.Fail(); + } + } + } + + [Fact] + public void CompleteDictionaryNoChangeNoSetTest() + { + TestMergeSetter first = new() + { + Backing = new Dictionary + { + [2] = 3 + } + }; + TestMergeSetter second = new() + { + Backing = new Dictionary() + }; + Merger.Complete(first, second); + // This should no call the setter of first so the test should pass. + } + + [Fact] + public void MergeDictionaryNoChangeNoSetTest() + { + TestMergeSetter first = new() + { + Backing = new Dictionary + { + [2] = 3 + } + }; + TestMergeSetter second = new() + { + Backing = new Dictionary() + }; + Merger.Merge(first, second); + // This should no call the setter of first so the test should pass. + } + } +} \ No newline at end of file diff --git a/Kyoo.Tests/Utility/TaskTests.cs b/tests/Kyoo.Tests/Utility/TaskTests.cs similarity index 100% rename from Kyoo.Tests/Utility/TaskTests.cs rename to tests/Kyoo.Tests/Utility/TaskTests.cs diff --git a/Kyoo.Tests/Utility/UtilityTests.cs b/tests/Kyoo.Tests/Utility/UtilityTests.cs similarity index 100% rename from Kyoo.Tests/Utility/UtilityTests.cs rename to tests/Kyoo.Tests/Utility/UtilityTests.cs