diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs index 792e1978..194fc2b4 100644 --- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -85,11 +85,6 @@ namespace Kyoo.Abstractions.Controllers /// IGenreRepository GenreRepository { get; } - /// - /// The repository that handle providers. - /// - IProviderRepository ProviderRepository { get; } - /// /// The repository that handle users. /// diff --git a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs index a45827f0..5c8e438d 100644 --- a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs +++ b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs @@ -446,25 +446,6 @@ namespace Kyoo.Abstractions.Controllers Pagination limit = default); } - /// - /// A repository to handle providers. - /// - public interface IProviderRepository : IRepository - { - /// - /// Get a list of external ids that match all filters - /// - /// A predicate to add arbitrary filter - /// Sort information (sort order and sort by) - /// 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> where = null, - Sort sort = default, - Pagination limit = default) - where T : class, IMetadata; - } - /// /// A repository to handle users. /// diff --git a/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs b/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs index 69ffe0bc..8ef426c5 100644 --- a/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs @@ -35,22 +35,20 @@ namespace Kyoo.Abstractions.Controllers /// /// The item to cache images. /// - /// - /// true if images should be downloaded even if they already exists locally, false otherwise. - /// /// The type of the item /// true if an image has been downloaded, false otherwise. - Task DownloadImages(T item, bool alwaysDownload = false) + Task DownloadImages(T item) where T : IThumbnails; /// /// 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 ID of the image. + /// The quality of the image /// The type of the item /// The path of the image for the given resource or null if it does not exists. - string? GetImagePath(T item, int imageId) + string GetImagePath(T item, string image, ImageQuality quality) where T : IThumbnails; } } diff --git a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs index fd9a5b85..852e48f9 100644 --- a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs +++ b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs @@ -85,7 +85,13 @@ namespace Kyoo.Abstractions.Models public DateTime? EndAir { get; set; } /// - public Dictionary Images { get; set; } + public Image Poster { get; set; } + + /// + public Image Thumbnail { get; set; } + + /// + public Image Logo { get; set; } /// /// The type of this item (ether a collection, a show or a movie). @@ -110,7 +116,9 @@ namespace Kyoo.Abstractions.Models Status = show.Status; StartAir = show.StartAir; EndAir = show.EndAir; - Images = show.Images; + Poster = show.Poster; + Thumbnail = show.Thumbnail; + Logo = show.Logo; Type = show.IsMovie ? ItemType.Movie : ItemType.Show; } @@ -127,7 +135,9 @@ namespace Kyoo.Abstractions.Models Status = Models.Status.Unknown; StartAir = null; EndAir = null; - Images = collection.Images; + Poster = collection.Poster; + Thumbnail = collection.Thumbnail; + Logo = collection.Logo; Type = ItemType.Collection; } @@ -143,7 +153,9 @@ namespace Kyoo.Abstractions.Models Status = x.Status, StartAir = x.StartAir, EndAir = x.EndAir, - Images = x.Images, + Poster = x.Poster, + Thumbnail = x.Thumbnail, + Logo = x.Logo, Type = x.IsMovie ? ItemType.Movie : ItemType.Show }; @@ -159,7 +171,9 @@ namespace Kyoo.Abstractions.Models Status = Models.Status.Unknown, StartAir = null, EndAir = null, - Images = x.Images, + Poster = x.Poster, + Thumbnail = x.Thumbnail, + Logo = x.Logo, Type = ItemType.Collection }; diff --git a/back/src/Kyoo.Abstractions/Models/MetadataID.cs b/back/src/Kyoo.Abstractions/Models/MetadataID.cs index dbf00a3e..0ab9b8fb 100644 --- a/back/src/Kyoo.Abstractions/Models/MetadataID.cs +++ b/back/src/Kyoo.Abstractions/Models/MetadataID.cs @@ -14,11 +14,6 @@ // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using System; -using System.Linq.Expressions; -using Kyoo.Abstractions.Models.Attributes; namespace Kyoo.Abstractions.Models { @@ -27,29 +22,6 @@ namespace Kyoo.Abstractions.Models /// public class MetadataID { - /// - /// The expression to retrieve the unique ID of a MetadataID. This is an aggregate of the two resources IDs. - /// - public static Expression> PrimaryKey - { - get { return x => new { First = x.ResourceID, Second = x.ProviderID }; } - } - - /// - /// 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. /// diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs index 079c5eef..e3fa14cf 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs @@ -39,7 +39,13 @@ namespace Kyoo.Abstractions.Models public string Name { get; set; } /// - public Dictionary Images { get; set; } + public Image Poster { get; set; } + + /// + public Image Thumbnail { get; set; } + + /// + public Image Logo { get; set; } /// /// The description of this collection. @@ -57,6 +63,6 @@ namespace Kyoo.Abstractions.Models [LoadableRelation] public ICollection Libraries { get; set; } /// - [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; } + public Dictionary ExternalId { get; set; } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index f6dcb41e..86e11442 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -127,9 +127,6 @@ namespace Kyoo.Abstractions.Models /// public string Path { get; set; } - /// - public Dictionary Images { get; set; } - /// /// The title of this episode. /// @@ -146,7 +143,16 @@ namespace Kyoo.Abstractions.Models public DateTime? ReleaseDate { get; set; } /// - [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; } + public Image Poster { get; set; } + + /// + public Image Thumbnail { get; set; } + + /// + public Image Logo { get; set; } + + /// + public Dictionary ExternalId { get; set; } /// /// Get the slug of an episode. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs index 44f072fa..d46a029e 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs @@ -16,11 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Kyoo.Abstractions.Models.Attributes; namespace Kyoo.Abstractions.Models { @@ -32,67 +28,6 @@ namespace Kyoo.Abstractions.Models /// /// 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; - } + public Dictionary ExternalId { get; set; } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs index 071ddc35..c69c984d 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs @@ -16,8 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System.Collections.Generic; -using Kyoo.Abstractions.Controllers; +using System.ComponentModel.DataAnnotations; namespace Kyoo.Abstractions.Models { @@ -25,53 +24,56 @@ namespace Kyoo.Abstractions.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. - /// - /// - /// An arbitrary index should not be used, instead use indexes from - /// - /// {"0": "example.com/dune/poster"} - public Dictionary Images { get; set; } - } - - /// - /// 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; + public Image Poster { get; set; } /// /// 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; + public Image Thumbnail { get; set; } /// /// A logo is a small image representing the resource. /// - public const int Logo = 2; + public Image Logo { get; set; } + } + + public class Image + { + /// + /// The original image from another server. + /// + public string Source { get; set; } /// - /// A video of a few minutes that tease the content. + /// A hash to display as placeholder while the image is loading. /// - public const int Trailer = 3; + [MaxLength(32)] + public string Blurhash { get; set; } + } + + /// + /// The quality of an image + /// + public enum ImageQuality + { + /// + /// Small + /// + Small, /// - /// Retrieve the name of an image using it's ID. It is also used by the serializer to retrieve all named images. - /// If a plugin adds a new image type, it should add it's value and name here to allow the serializer to add it. + /// Medium /// - public static Dictionary ImageName { get; } = new() - { - [Poster] = nameof(Poster), - [Thumbnail] = nameof(Thumbnail), - [Logo] = nameof(Logo), - [Trailer] = nameof(Trailer) - }; + Medium, + + /// + /// Large + /// + Large, } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Library.cs b/back/src/Kyoo.Abstractions/Models/Resources/Library.cs index e56bb352..0c4129f4 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Library.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Library.cs @@ -42,11 +42,6 @@ namespace Kyoo.Abstractions.Models /// public string[] Paths { get; set; } - /// - /// The list of used for items in this library. - /// - [EditableRelation][LoadableRelation] public ICollection Providers { get; set; } - /// /// The list of shows in this library. /// diff --git a/back/src/Kyoo.Abstractions/Models/Resources/People.cs b/back/src/Kyoo.Abstractions/Models/Resources/People.cs index 9ed2cea3..c5a32429 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/People.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/People.cs @@ -38,10 +38,16 @@ namespace Kyoo.Abstractions.Models public string Name { get; set; } /// - public Dictionary Images { get; set; } + public Image Poster { get; set; } /// - [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; } + public Image Thumbnail { get; set; } + + /// + public Image Logo { get; set; } + + /// + public Dictionary ExternalId { get; set; } /// /// The list of roles this person has played in. See for more information. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Provider.cs b/back/src/Kyoo.Abstractions/Models/Resources/Provider.cs deleted file mode 100644 index 4e419027..00000000 --- a/back/src/Kyoo.Abstractions/Models/Resources/Provider.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Kyoo - A portable and vast media library solution. -// Copyright (c) Kyoo. -// -// See AUTHORS.md and LICENSE file in the project root for full license information. -// -// Kyoo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// Kyoo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using System.Collections.Generic; -using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Utils; - -namespace Kyoo.Abstractions.Models -{ - /// - /// A dead class that will be removed later. - /// - // TODO: Delete this class - public class Provider : IResource, IThumbnails - { - /// - public int ID { get; set; } - - /// - public string Slug { get; set; } - - /// - /// The name of this provider. - /// - public string Name { get; set; } - - /// - public Dictionary Images { get; set; } - - /// - /// The list of libraries that uses this provider. - /// - [LoadableRelation] public ICollection Libraries { get; set; } - - /// - /// Create a new, default, - /// - public Provider() { } - - /// - /// Create a new and specify it's . - /// The is automatically calculated from it's name. - /// - /// The name of this provider. - /// The logo of this provider. - public Provider(string name, string logo) - { - Slug = Utility.ToSlug(name); - Name = name; - Images = new Dictionary - { - [Models.Images.Logo] = logo - }; - } - } -} diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs index 91063b8b..1b31d2c3 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs @@ -99,10 +99,16 @@ namespace Kyoo.Abstractions.Models public DateTime? EndDate { get; set; } /// - public Dictionary Images { get; set; } + public Image Poster { get; set; } /// - [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; } + public Image Thumbnail { get; set; } + + /// + public Image Logo { get; set; } + + /// + public Dictionary ExternalId { get; set; } /// /// The list of episodes that this season contains. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs index 646dee9f..0dc30b30 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -59,13 +59,6 @@ namespace Kyoo.Abstractions.Models /// public Status Status { get; set; } - /// - /// An URL to a trailer. - /// - /// TODO for now, this is set to a youtube url. It should be cached and converted to a local file. - [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. /// @@ -78,16 +71,27 @@ namespace Kyoo.Abstractions.Models /// public DateTime? EndAir { get; set; } - /// - public Dictionary Images { get; set; } - /// /// True if this show represent a movie, false otherwise. /// public bool IsMovie { get; set; } /// - [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; } + public Image Poster { get; set; } + + /// + public Image Thumbnail { get; set; } + + /// + public Image Logo { get; set; } + + /// + /// A video of a few minutes that tease the content. + /// + public string Trailer { get; set; } + + /// + public Dictionary ExternalId { get; set; } /// /// The ID of the Studio that made this show. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs b/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs index 973404d2..e3923d3a 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs @@ -44,7 +44,7 @@ namespace Kyoo.Abstractions.Models [LoadableRelation] public ICollection Shows { get; set; } /// - [EditableRelation][LoadableRelation] public ICollection ExternalIDs { get; set; } + public Dictionary ExternalId { get; set; } /// /// Create a new, empty, . diff --git a/back/src/Kyoo.Abstractions/Models/Resources/User.cs b/back/src/Kyoo.Abstractions/Models/Resources/User.cs index cf9f5368..2fde3468 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/User.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/User.cs @@ -24,7 +24,7 @@ namespace Kyoo.Abstractions.Models /// /// A single user of the app. /// - public class User : IResource, IThumbnails + public class User : IResource { /// public int ID { get; set; } @@ -59,8 +59,10 @@ namespace Kyoo.Abstractions.Models [SerializeIgnore] public Dictionary ExtraData { get; set; } - /// - public Dictionary Images { get; set; } + /// + /// A logo is a small image representing the resource. + /// + public Image Logo { get; set; } /// /// The list of shows the user has finished. diff --git a/back/src/Kyoo.Abstractions/Models/WatchItem.cs b/back/src/Kyoo.Abstractions/Models/WatchItem.cs index ca87d1e7..7ec67e4b 100644 --- a/back/src/Kyoo.Abstractions/Models/WatchItem.cs +++ b/back/src/Kyoo.Abstractions/Models/WatchItem.cs @@ -103,7 +103,13 @@ namespace Kyoo.Abstractions.Models public bool IsMovie { get; set; } /// - public Dictionary Images { get; set; } + public Image Poster { get; set; } + + /// + public Image Thumbnail { get; set; } + + /// + public Image Logo { get; set; } /// /// The transcoder's info for this item. This include subtitles, fonts, chapters... @@ -145,7 +151,9 @@ namespace Kyoo.Abstractions.Models Title = ep.Title, Overview = ep.Overview, ReleaseDate = ep.ReleaseDate, - Images = ep.Show.Images, + Poster = ep.Poster, + Thumbnail = ep.Thumbnail, + Logo = ep.Logo, PreviousEpisode = ep.Show.IsMovie ? null : (await library.GetAll( diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs index 453d6b10..edced1e7 100644 --- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -66,9 +66,6 @@ namespace Kyoo.Core.Controllers /// public IGenreRepository GenreRepository { get; } - /// - public IProviderRepository ProviderRepository { get; } - /// public IUserRepository UserRepository { get; } @@ -89,7 +86,6 @@ namespace Kyoo.Core.Controllers PeopleRepository = GetRepository() as IPeopleRepository; StudioRepository = GetRepository() as IStudioRepository; GenreRepository = GetRepository() as IGenreRepository; - ProviderRepository = GetRepository() as IProviderRepository; UserRepository = GetRepository() as IUserRepository; } @@ -259,10 +255,6 @@ namespace Kyoo.Core.Controllers return (obj, member: memberName) switch { - (Library l, nameof(Library.Providers)) => ProviderRepository - .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) - .Then(x => l.Providers = x), - (Library l, nameof(Library.Shows)) => ShowRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) .Then(x => l.Shows = x), @@ -272,11 +264,6 @@ namespace Kyoo.Core.Controllers .Then(x => l.Collections = x), - (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), @@ -286,11 +273,6 @@ namespace Kyoo.Core.Controllers .Then(x => c.Libraries = x), - (Show s, nameof(Show.ExternalIDs)) => _SetRelation(s, - ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), - (x, y) => x.ExternalIDs = y, - (x, y) => { x.ResourceID = y.ID; }), - (Show s, nameof(Show.Genres)) => GenreRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Genres = x), @@ -326,11 +308,6 @@ namespace Kyoo.Core.Controllers }), - (Season s, nameof(Season.ExternalIDs)) => _SetRelation(s, - ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), - (x, y) => x.ExternalIDs = y, - (x, y) => { x.ResourceID = y.ID; }), - (Season s, nameof(Season.Episodes)) => _SetRelation(s, EpisodeRepository.GetAll(x => x.Season.ID == obj.ID), (x, y) => x.Episodes = y, @@ -345,11 +322,6 @@ namespace Kyoo.Core.Controllers }), - (Episode e, nameof(Episode.ExternalIDs)) => _SetRelation(e, - ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), - (x, y) => x.ExternalIDs = y, - (x, y) => { x.ResourceID = y.ID; }), - (Episode e, nameof(Episode.Show)) => ShowRepository .GetOrDefault(x => x.Episodes.Any(y => y.ID == obj.ID)) .Then(x => @@ -376,27 +348,10 @@ namespace Kyoo.Core.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.ResourceID == obj.ID), - (x, y) => x.ExternalIDs = y, - (x, y) => { x.ResourceID = y.ID; }), - (People p, nameof(People.Roles)) => PeopleRepository .GetFromPeople(obj.ID) .Then(x => p.Roles = x), - - (Provider p, nameof(Provider.Libraries)) => LibraryRepository - .GetAll(x => x.Providers.Any(y => y.ID == obj.ID)) - .Then(x => p.Libraries = x), - - _ => throw new ArgumentException($"Couldn't find a way to load {memberName} of {obj.Slug}.") }; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs index 53f8df1b..d8489e10 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs @@ -37,11 +37,6 @@ namespace Kyoo.Core.Controllers /// private readonly DatabaseContext _database; - /// - /// A provider repository to handle externalID creation and deletion - /// - private readonly IProviderRepository _providers; - /// protected override Sort DefaultSort => new Sort.By(nameof(Collection.Name)); @@ -49,12 +44,10 @@ namespace Kyoo.Core.Controllers /// Create a new . /// /// The database handle to use - /// /// A provider repository - public CollectionRepository(DatabaseContext database, IProviderRepository providers) + public CollectionRepository(DatabaseContext database) : base(database) { _database = database; - _providers = providers; } /// @@ -82,33 +75,8 @@ namespace Kyoo.Core.Controllers { 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; - } } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index c3fc9ec8..eb58fe98 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -39,11 +39,6 @@ namespace Kyoo.Core.Controllers /// private readonly DatabaseContext _database; - /// - /// A provider repository to handle externalID creation and deletion - /// - private readonly IProviderRepository _providers; - private readonly IShowRepository _shows; /// @@ -59,14 +54,11 @@ namespace Kyoo.Core.Controllers /// /// The database handle to use. /// A show repository - /// A provider repository public EpisodeRepository(DatabaseContext database, - IShowRepository shows, - IProviderRepository providers) + IShowRepository shows) : base(database) { _database = database; - _providers = providers; _shows = shows; // Edit episode slugs when the show's slug changes. @@ -160,18 +152,6 @@ namespace Kyoo.Core.Controllers return obj; } - /// - protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld) - { - await Validate(changed); - - if (changed.ExternalIDs != null || resetOld) - { - await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); - resource.ExternalIDs = changed.ExternalIDs; - } - } - /// protected override async Task Validate(Episode resource) { @@ -185,17 +165,6 @@ namespace Kyoo.Core.Controllers } 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); - } } /// @@ -206,7 +175,6 @@ namespace Kyoo.Core.Controllers int epCount = await _database.Episodes.Where(x => x.ShowID == obj.ShowID).Take(2).CountAsync(); _database.Entry(obj).State = EntityState.Deleted; - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted); await _database.SaveChangesAsync(); await base.Delete(obj); if (epCount == 1) diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs index dfc76442..59e7083c 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs @@ -38,11 +38,6 @@ namespace Kyoo.Core.Controllers /// private readonly DatabaseContext _database; - /// - /// A provider repository to handle externalID creation and deletion - /// - private readonly IProviderRepository _providers; - /// protected override Sort DefaultSort => new Sort.By(x => x.ID); @@ -50,12 +45,10 @@ namespace Kyoo.Core.Controllers /// Create a new instance. /// /// The database handle - /// The provider repository - public LibraryRepository(DatabaseContext database, IProviderRepository providers) + public LibraryRepository(DatabaseContext database) : base(database) { _database = database; - _providers = providers; } /// @@ -90,29 +83,6 @@ namespace Kyoo.Core.Controllers 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(async x => - _database.LocalEntity(x.Slug) - ?? await _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 Database.Entry(resource).Collection(x => x.Providers).LoadAsync(); - resource.Providers = changed.Providers; - } } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 85c3b6a3..f2b4cf56 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -341,6 +341,8 @@ namespace Kyoo.Core.Controllers if (obj == null) throw new ArgumentNullException(nameof(obj)); await Validate(obj); + // if (obj is IThumbnails thumbs) + // await _thumbnailsManager.DownloadImages(thumbs); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs index bd631ed1..766629e0 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs @@ -39,11 +39,6 @@ namespace Kyoo.Core.Controllers /// private readonly DatabaseContext _database; - /// - /// A provider repository to handle externalID creation and deletion - /// - private readonly IProviderRepository _providers; - /// /// A lazy loaded show repository to validate requests from shows. /// @@ -56,15 +51,12 @@ namespace Kyoo.Core.Controllers /// Create a new /// /// The database handle - /// A provider repository /// A lazy loaded show repository public PeopleRepository(DatabaseContext database, - IProviderRepository providers, Lazy shows) : base(database) { _database = database; - _providers = providers; _shows = shows; } @@ -94,17 +86,6 @@ namespace Kyoo.Core.Controllers { 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); - } - if (resource.Roles != null) { foreach (PeopleRole role in resource.Roles) @@ -127,12 +108,6 @@ namespace Kyoo.Core.Controllers await Database.Entry(resource).Collection(x => x.Roles).LoadAsync(); resource.Roles = changed.Roles; } - - if (changed.ExternalIDs != null || resetOld) - { - await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); - resource.ExternalIDs = changed.ExternalIDs; - } } /// @@ -142,7 +117,6 @@ namespace Kyoo.Core.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted); obj.Roles.ForEach(x => _database.Entry(x).State = EntityState.Deleted); await _database.SaveChangesAsync(); await base.Delete(obj); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs deleted file mode 100644 index 7435e0fa..00000000 --- a/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Kyoo - A portable and vast media library solution. -// Copyright (c) Kyoo. -// -// See AUTHORS.md and LICENSE file in the project root for full license information. -// -// Kyoo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// Kyoo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Kyoo.Postgresql; -using Microsoft.EntityFrameworkCore; - -namespace Kyoo.Core.Controllers -{ - /// - /// A local repository to handle providers. - /// - public class ProviderRepository : LocalRepository, IProviderRepository - { - /// - /// The database handle - /// - private readonly DatabaseContext _database; - - /// - /// Create a new . - /// - /// The database handle - public ProviderRepository(DatabaseContext database) - : base(database) - { - _database = database; - } - - /// - protected override Sort DefaultSort => new Sort.By(x => x.Slug); - - /// - public override async Task> Search(string query) - { - return await Sort( - _database.Providers - .Where(_database.Like(x => x.Name, $"%{query}%")) - ) - .Take(20) - .ToListAsync(); - } - - /// - public override async Task Create(Provider obj) - { - await base.Create(obj); - _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync(() => Get(obj.Slug)); - OnResourceCreated(obj); - return obj; - } - - /// - public override async Task Delete(Provider obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - _database.Entry(obj).State = EntityState.Deleted; - await _database.SaveChangesAsync(); - await base.Delete(obj); - } - - /// - public async Task> GetMetadataID(Expression> where = null, - Sort sort = default, - Pagination limit = default) - where T : class, IMetadata - { - return await _database.MetadataIds() - .Include(y => y.Provider) - .Where(where) - .ToListAsync(); - } - } -} diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index de070572..7b12cf73 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -38,11 +38,6 @@ namespace Kyoo.Core.Controllers /// private readonly DatabaseContext _database; - /// - /// A provider repository to handle externalID creation and deletion - /// - private readonly IProviderRepository _providers; - /// protected override Sort DefaultSort => new Sort.By(x => x.SeasonNumber); @@ -51,14 +46,11 @@ namespace Kyoo.Core.Controllers /// /// The database handle that will be used /// A shows repository - /// A provider repository public SeasonRepository(DatabaseContext database, - IShowRepository shows, - IProviderRepository providers) + IShowRepository shows) : base(database) { _database = database; - _providers = providers; // Edit seasons slugs when the show's slug changes. shows.OnEdited += (show) => @@ -140,29 +132,6 @@ namespace Kyoo.Core.Controllers } 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); - } - } - - /// - 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; - } } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index e9ea389e..4e2092b2 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -52,11 +52,6 @@ namespace Kyoo.Core.Controllers /// private readonly IGenreRepository _genres; - /// - /// A provider repository to handle externalID creation and deletion - /// - private readonly IProviderRepository _providers; - /// protected override Sort DefaultSort => new Sort.By(x => x.Title); @@ -67,19 +62,16 @@ namespace Kyoo.Core.Controllers /// A studio repository /// A people repository /// A genres repository - /// A provider repository public ShowRepository(DatabaseContext database, IStudioRepository studios, IPeopleRepository people, - IGenreRepository genres, - IProviderRepository providers) + IGenreRepository genres) : base(database) { _database = database; _studios = studios; _people = people; _genres = genres; - _providers = providers; } /// @@ -124,17 +116,6 @@ namespace Kyoo.Core.Controllers _database.AttachRange(resource.Genres); } - 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); - } - if (resource.People != null) { foreach (PeopleRole role in resource.People) @@ -172,12 +153,6 @@ namespace Kyoo.Core.Controllers await Database.Entry(resource).Collection(x => x.People).LoadAsync(); resource.People = changed.People; } - - if (changed.ExternalIDs != null || resetOld) - { - await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); - resource.ExternalIDs = changed.ExternalIDs; - } } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs index 138c7ae1..738f3fb4 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs @@ -38,11 +38,6 @@ namespace Kyoo.Core.Controllers /// private readonly DatabaseContext _database; - /// - /// A provider repository to handle externalID creation and deletion - /// - private readonly IProviderRepository _providers; - /// protected override Sort DefaultSort => new Sort.By(x => x.Name); @@ -50,12 +45,10 @@ namespace Kyoo.Core.Controllers /// Create a new . /// /// The database handle - /// A provider repository - public StudioRepository(DatabaseContext database, IProviderRepository providers) + public StudioRepository(DatabaseContext database) : base(database) { _database = database; - _providers = providers; } /// @@ -83,30 +76,7 @@ namespace Kyoo.Core.Controllers protected override async Task Validate(Studio resource) { resource.Slug ??= Utility.ToSlug(resource.Name); - 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); } /// diff --git a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs index 15ba7def..4d561b2a 100644 --- a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -18,13 +18,12 @@ using System; using System.IO; -using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; -using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Logging; +using SkiaSharp; #nullable enable @@ -54,18 +53,18 @@ namespace Kyoo.Core.Controllers _logger = logger; } - /// - /// An helper function to download an image. - /// - /// The distant url of the image - /// The local path of the image - /// What is currently downloaded (used for errors) - /// true if an image has been downloaded, false otherwise. - private async Task _DownloadImage(string url, string localPath, string what) + private static async Task _WriteTo(SKBitmap bitmap, string path) { - if (url == localPath) - return false; + SKData data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 18); + await using Stream reader = data.AsStream(); + await using Stream file = File.Create(path); + await reader.CopyToAsync(file); + } + private async Task _DownloadImage(string? url, string localPath, string what) + { + if (url == null) + return; try { _logger.LogInformation("Downloading image {What}", what); @@ -73,86 +72,53 @@ namespace Kyoo.Core.Controllers HttpClient client = _clientFactory.CreateClient(); HttpResponseMessage response = await client.GetAsync(url); response.EnsureSuccessStatusCode(); - string mime = response.Content.Headers.ContentType?.MediaType!; await using Stream reader = await response.Content.ReadAsStreamAsync(); + SKBitmap bitmap = SKBitmap.Decode(reader); - string extension = new FileExtensionContentTypeProvider() - .Mappings.FirstOrDefault(x => x.Value == mime) - .Key; - await using Stream local = File.Create(localPath + extension); - await reader.CopyToAsync(local); - return true; + bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.High); + await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Large.ToString().ToLowerInvariant()}.jpg"); + + bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.Medium); + await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Medium.ToString().ToLowerInvariant()}.jpg"); + + bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.Low); + await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Small.ToString().ToLowerInvariant()}.jpg"); } catch (Exception ex) { _logger.LogError(ex, "{What} could not be downloaded", what); - return false; } } /// - public async Task DownloadImages(T item, bool alwaysDownload = false) + public async Task DownloadImages(T item) where T : IThumbnails { 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 = _GetPrivateImagePath(item, id); - if (alwaysDownload || !Path.Exists(localPath)) - ret |= await _DownloadImage(image, localPath, $"The image n {id} of {name}"); - } - - return ret; + await _DownloadImage(item.Poster.Source, _GetBaseImagePath(item, "poster"), $"The poster of {name}"); + await _DownloadImage(item.Thumbnail.Source, _GetBaseImagePath(item, "thumbnail"), $"The poster of {name}"); + await _DownloadImage(item.Logo.Source, _GetBaseImagePath(item, "logo"), $"The poster of {name}"); } - /// - /// Retrieve the local path of an image of the given item without an extension. - /// - /// 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 static string _GetPrivateImagePath(T item, int imageId) + private static string _GetBaseImagePath(T item, string image) { - if (item == null) - throw new ArgumentNullException(nameof(item)); - string directory = item switch { IResource res => Path.Combine("./metadata", typeof(T).Name.ToLowerInvariant(), res.Slug), _ => Path.Combine("./metadata", typeof(T).Name.ToLowerInvariant()) }; Directory.CreateDirectory(directory); - string imageName = imageId switch - { - Images.Poster => "poster", - Images.Logo => "logo", - Images.Thumbnail => "thumbnail", - Images.Trailer => "trailer", - _ => $"{imageId}" - }; - return Path.Combine(directory, imageName); + return Path.Combine(directory, image); } /// - public string? GetImagePath(T item, int imageId) + public string GetImagePath(T item, string image, ImageQuality quality) where T : IThumbnails { - string basePath = _GetPrivateImagePath(item, imageId); - string directory = Path.GetDirectoryName(basePath)!; - string baseFile = Path.GetFileName(basePath); - if (!Directory.Exists(directory)) - return null; - return Directory.GetFiles(directory, "*", SearchOption.TopDirectoryOnly) - .FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == baseFile); + return $"{_GetBaseImagePath(item, image)}.{quality.ToString().ToLowerInvariant()}.jpg"; } } } diff --git a/back/src/Kyoo.Core/CoreModule.cs b/back/src/Kyoo.Core/CoreModule.cs index 5c55c01b..162ce4bc 100644 --- a/back/src/Kyoo.Core/CoreModule.cs +++ b/back/src/Kyoo.Core/CoreModule.cs @@ -57,7 +57,6 @@ namespace Kyoo.Core builder.RegisterRepository(); builder.RegisterRepository(); builder.RegisterRepository(); - builder.RegisterRepository(); builder.RegisterRepository(); } diff --git a/back/src/Kyoo.Core/Kyoo.Core.csproj b/back/src/Kyoo.Core/Kyoo.Core.csproj index 917b6986..0839dfe5 100644 --- a/back/src/Kyoo.Core/Kyoo.Core.csproj +++ b/back/src/Kyoo.Core/Kyoo.Core.csproj @@ -6,9 +6,11 @@ + + diff --git a/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs b/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs index 20a9715c..e2c9d6c4 100644 --- a/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs +++ b/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.IO; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; @@ -25,7 +24,6 @@ using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.StaticFiles; using static Kyoo.Abstractions.Models.Utils.Constants; namespace Kyoo.Core.Api @@ -59,37 +57,7 @@ namespace Kyoo.Core.Api _thumbs = thumbs; } - /// - /// Get the content type of a file using it's extension. - /// - /// The path of the file - /// The extension of the file is not known. - /// The content type of the file - private static string _GetContentType(string path) - { - FileExtensionContentTypeProvider provider = new(); - if (provider.TryGetContentType(path, out string contentType)) - return contentType; - throw new NotImplementedException($"Can't get the content type of the file at: {path}"); - } - - /// - /// Get image - /// - /// - /// Get an image for the specified item. - /// List of commonly available images:
- /// - Poster: Image 0, also available at /poster
- /// - Thumbnail: Image 1, also available at /thumbnail
- /// - Logo: Image 3, also available at /logo
- ///
- /// Other images can be arbitrarily added by plugins so any image number can be specified from this endpoint. - ///
- /// The ID or slug of the resource to get the image for. - /// The number of the image to retrieve. - /// The image asked. - /// No item exist with the specific identifier or the image does not exists on kyoo. - private async Task _GetImage(Identifier identifier, int image) + private async Task _GetImage(Identifier identifier, string image, ImageQuality? quality) { T resource = await identifier.Match( id => Repository.GetOrDefault(id), @@ -97,10 +65,10 @@ namespace Kyoo.Core.Api ); if (resource == null) return NotFound(); - string path = _thumbs.GetImagePath(resource, image); + string path = _thumbs.GetImagePath(resource, image, quality ?? ImageQuality.Large); if (path == null || !System.IO.File.Exists(path)) return NotFound(); - return PhysicalFile(Path.GetFullPath(path), _GetContentType(path), true); + return PhysicalFile(Path.GetFullPath(path), "image/jpeg", true); } /// @@ -110,17 +78,18 @@ namespace Kyoo.Core.Api /// Get the poster for the specified item. /// /// The ID or slug of the resource to get the image for. + /// The quality of the image to retrieve. /// The image asked. /// /// No item exist with the specific identifier or the image does not exists on kyoo. /// - [HttpGet("{identifier:id}/poster", Order = AlternativeRoute)] + [HttpGet("{identifier:id}/poster")] [PartialPermission(Kind.Read)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public Task GetPoster(Identifier identifier) + public Task GetPoster(Identifier identifier, [FromQuery] ImageQuality? quality) { - return _GetImage(identifier, Images.Poster); + return _GetImage(identifier, "poster", quality); } /// @@ -130,17 +99,18 @@ namespace Kyoo.Core.Api /// Get the logo for the specified item. /// /// The ID or slug of the resource to get the image for. + /// The quality of the image to retrieve. /// The image asked. /// /// No item exist with the specific identifier or the image does not exists on kyoo. /// - [HttpGet("{identifier:id}/logo", Order = AlternativeRoute)] + [HttpGet("{identifier:id}/logo")] [PartialPermission(Kind.Read)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public Task GetLogo(Identifier identifier) + public Task GetLogo(Identifier identifier, [FromQuery] ImageQuality? quality) { - return _GetImage(identifier, Images.Logo); + return _GetImage(identifier, "logo", quality); } /// @@ -150,15 +120,16 @@ namespace Kyoo.Core.Api /// Get the thumbnail for the specified item. /// /// The ID or slug of the resource to get the image for. + /// The quality of the image to retrieve. /// The image asked. /// /// No item exist with the specific identifier or the image does not exists on kyoo. /// + [HttpGet("{identifier:id}/thumbnail")] [HttpGet("{identifier:id}/backdrop", Order = AlternativeRoute)] - [HttpGet("{identifier:id}/thumbnail", Order = AlternativeRoute)] - public Task GetBackdrop(Identifier identifier) + public Task GetBackdrop(Identifier identifier, [FromQuery] ImageQuality? quality) { - return _GetImage(identifier, Images.Thumbnail); + return _GetImage(identifier, "thumbnail", quality); } /// diff --git a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs index a1394b87..c0b1d8bb 100644 --- a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs +++ b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs @@ -79,28 +79,28 @@ namespace Kyoo.Core.Api protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { IList properties = base.CreateProperties(type, memberSerialization); - if (!type.IsAssignableTo(typeof(IThumbnails))) + if (!type.IsAssignableTo(typeof(Image))) return properties; - foreach ((int id, string image) in Images.ImageName) - { - properties.Add(new JsonProperty - { - DeclaringType = type, - PropertyName = image.ToLower(), - UnderlyingName = image, - PropertyType = typeof(string), - Readable = true, - Writable = false, - ItemIsReference = false, - TypeNameHandling = TypeNameHandling.None, - ShouldSerialize = x => - { - IThumbnails thumb = (IThumbnails)x; - return thumb?.Images?.ContainsKey(id) == true; - }, - ValueProvider = new ThumbnailProvider(id) - }); - } + // foreach ((int id, string image) in Images.ImageName) + // { + // properties.Add(new JsonProperty + // { + // DeclaringType = type, + // PropertyName = image.ToLower(), + // UnderlyingName = image, + // PropertyType = typeof(string), + // Readable = true, + // Writable = false, + // ItemIsReference = false, + // TypeNameHandling = TypeNameHandling.None, + // ShouldSerialize = x => + // { + // IThumbnails thumb = (IThumbnails)x; + // return thumb?.Images?.ContainsKey(id) == true; + // }, + // ValueProvider = new ThumbnailProvider(id) + // }); + // } return properties; } @@ -128,23 +128,22 @@ namespace Kyoo.Core.Api /// public void SetValue(object target, object value) { - if (target is not IThumbnails thumb) - throw new ArgumentException($"The given object is not an Thumbnail."); - thumb.Images[_imageIndex] = value as string; + throw new NotSupportedException(); } /// public object GetValue(object target) { - string slug = (target as IResource)?.Slug ?? (target as ICustomTypeDescriptor)?.GetComponentName(); - if (target is not IThumbnails thumb - || slug == null - || string.IsNullOrEmpty(thumb.Images?.GetValueOrDefault(_imageIndex))) - return null; - string type = target is ICustomTypeDescriptor descriptor - ? descriptor.GetClassName() - : target.GetType().Name; - return $"/{type}/{slug}/{Images.ImageName[_imageIndex]}".ToLowerInvariant(); + return null; + // string slug = (target as IResource)?.Slug ?? (target as ICustomTypeDescriptor)?.GetComponentName(); + // if (target is not IThumbnails thumb + // || slug == null + // || string.IsNullOrEmpty(thumb.Images?.GetValueOrDefault(_imageIndex))) + // return null; + // string type = target is ICustomTypeDescriptor descriptor + // ? descriptor.GetClassName() + // : target.GetType().Name; + // return $"/{type}/{slug}/{Images.ImageName[_imageIndex]}".ToLowerInvariant(); } } } diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index 19303e75..b5a15d25 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -80,11 +81,6 @@ namespace Kyoo.Postgresql /// public DbSet Studios { get; set; } - /// - /// All providers of Kyoo. See . - /// - public DbSet Providers { get; set; } - /// /// The list of registered users. /// @@ -108,17 +104,6 @@ namespace Kyoo.Postgresql /// public DbSet LibraryItems { get; set; } - /// - /// 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, IMetadata - { - return Set(MetadataName()); - } - /// /// Add a many to many link between two resources. /// @@ -194,17 +179,33 @@ namespace Kyoo.Postgresql /// /// The database model builder /// The type to add metadata to. - private void _HasMetadata(ModelBuilder modelBuilder) + private static void _HasMetadata(ModelBuilder modelBuilder) where T : class, IMetadata { - modelBuilder.SharedTypeEntity(MetadataName()) - .HasKey(MetadataID.PrimaryKey); + // TODO: Waiting for https://github.com/dotnet/efcore/issues/29825 + // modelBuilder.Entity() + // .OwnsOne(x => x.ExternalIDs, x => + // { + // x.ToJson(); + // }); + modelBuilder.Entity() + .Property(x => x.ExternalId) + .HasConversion( + v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null), + v => JsonSerializer.Deserialize>(v, (JsonSerializerOptions)null) + ) + .HasColumnType("json"); + } - modelBuilder.SharedTypeEntity(MetadataName()) - .HasOne() - .WithMany(x => x.ExternalIDs) - .HasForeignKey(x => x.ResourceID) - .OnDelete(DeleteBehavior.Cascade); + private static void _HasImages(ModelBuilder modelBuilder) + where T : class, IThumbnails + { + modelBuilder.Entity() + .OwnsOne(x => x.Poster); + modelBuilder.Entity() + .OwnsOne(x => x.Thumbnail); + modelBuilder.Entity() + .OwnsOne(x => x.Logo); } /// @@ -269,7 +270,6 @@ namespace Kyoo.Postgresql .WithMany(x => x.Shows) .OnDelete(DeleteBehavior.SetNull); - _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); @@ -287,6 +287,15 @@ namespace Kyoo.Postgresql _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); + _HasImages(modelBuilder); + _HasImages(modelBuilder); + _HasImages(modelBuilder); + _HasImages(modelBuilder); + _HasImages(modelBuilder); + _HasImages(modelBuilder); + + modelBuilder.Entity().OwnsOne(x => x.Logo); + modelBuilder.Entity() .HasKey(x => new { User = x.UserID, Episode = x.EpisodeID }); @@ -294,7 +303,6 @@ namespace Kyoo.Postgresql modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); - modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); @@ -319,9 +327,6 @@ namespace Kyoo.Postgresql modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); - modelBuilder.Entity() - .HasIndex(x => x.Slug) - .IsUnique(); modelBuilder.Entity() .HasIndex(x => new { x.ShowID, x.SeasonNumber }) .IsUnique(); diff --git a/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.Designer.cs new file mode 100644 index 00000000..cc630b9f --- /dev/null +++ b/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.Designer.cs @@ -0,0 +1,1339 @@ +// +using System; +using System.Collections.Generic; +using Kyoo.Abstractions.Models; +using Kyoo.Postgresql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Kyoo.Postgresql.Migrations +{ + [DbContext(typeof(PostgresContext))] + [Migration("20230804143919_AddBlurhash")] + partial class AddBlurhash + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_type", new[] { "show", "movie", "collection" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.HasKey("ID") + .HasName("pk_collections"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_collections_slug"); + + b.ToTable("collections", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("AbsoluteNumber") + .HasColumnType("integer") + .HasColumnName("absolute_number"); + + b.Property("EpisodeNumber") + .HasColumnType("integer") + .HasColumnName("episode_number"); + + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("ReleaseDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("release_date"); + + b.Property("SeasonID") + .HasColumnType("integer") + .HasColumnName("season_id"); + + b.Property("SeasonNumber") + .HasColumnType("integer") + .HasColumnName("season_number"); + + b.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("ID") + .HasName("pk_episodes"); + + b.HasIndex("SeasonID") + .HasDatabaseName("ix_episodes_season_id"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_episodes_slug"); + + b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") + .IsUnique() + .HasDatabaseName("ix_episodes_show_id_season_number_episode_number_absolute_numb"); + + b.ToTable("episodes", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Genre", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.HasKey("ID") + .HasName("pk_genres"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_genres_slug"); + + b.ToTable("genres", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Library", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Paths") + .HasColumnType("text[]") + .HasColumnName("paths"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.HasKey("ID") + .HasName("pk_libraries"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_libraries_slug"); + + b.ToTable("libraries", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + { + b.Property("ID") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("EndAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_air"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Slug") + .HasColumnType("text") + .HasColumnName("slug"); + + b.Property("StartAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_air"); + + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("Type") + .HasColumnType("item_type") + .HasColumnName("type"); + + b.HasKey("ID") + .HasName("pk_library_items"); + + b.ToTable((string)null); + + b.ToView("library_items", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.HasKey("ID") + .HasName("pk_people"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_people_slug"); + + b.ToTable("people", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("PeopleID") + .HasColumnType("integer") + .HasColumnName("people_id"); + + b.Property("Role") + .HasColumnType("text") + .HasColumnName("role"); + + b.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.Property("Type") + .HasColumnType("text") + .HasColumnName("type"); + + b.HasKey("ID") + .HasName("pk_people_roles"); + + b.HasIndex("PeopleID") + .HasDatabaseName("ix_people_roles_people_id"); + + b.HasIndex("ShowID") + .HasDatabaseName("ix_people_roles_show_id"); + + b.ToTable("people_roles", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_date"); + + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("SeasonNumber") + .HasColumnType("integer") + .HasColumnName("season_number"); + + b.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("ID") + .HasName("pk_seasons"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_seasons_slug"); + + b.HasIndex("ShowID", "SeasonNumber") + .IsUnique() + .HasDatabaseName("ix_seasons_show_id_season_number"); + + b.ToTable("seasons", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("Aliases") + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("EndAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_air"); + + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); + + b.Property("IsMovie") + .HasColumnType("boolean") + .HasColumnName("is_movie"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.Property("StartAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_air"); + + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); + + b.Property("StudioID") + .HasColumnType("integer") + .HasColumnName("studio_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); + + b.HasKey("ID") + .HasName("pk_shows"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_shows_slug"); + + b.HasIndex("StudioID") + .HasDatabaseName("ix_shows_studio_id"); + + b.ToTable("shows", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.HasKey("ID") + .HasName("pk_studios"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_studios_slug"); + + b.ToTable("studios", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property>("ExtraData") + .HasColumnType("jsonb") + .HasColumnName("extra_data"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Permissions") + .HasColumnType("text[]") + .HasColumnName("permissions"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("ID") + .HasName("pk_users"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_users_slug"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.WatchedEpisode", b => + { + b.Property("UserID") + .HasColumnType("integer") + .HasColumnName("user_id"); + + b.Property("EpisodeID") + .HasColumnType("integer") + .HasColumnName("episode_id"); + + b.Property("WatchedPercentage") + .HasColumnType("integer") + .HasColumnName("watched_percentage"); + + b.HasKey("UserID", "EpisodeID") + .HasName("pk_watched_episodes"); + + b.HasIndex("EpisodeID") + .HasDatabaseName("ix_watched_episodes_episode_id"); + + b.ToTable("watched_episodes", (string)null); + }); + + 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", (string)null); + }); + + 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", (string)null); + }); + + 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", (string)null); + }); + + 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", (string)null); + }); + + 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", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("CollectionID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("CollectionID"); + + b1.ToTable("collections"); + + b1.WithOwner() + .HasForeignKey("CollectionID") + .HasConstraintName("fk_collections_collections_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("CollectionID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("CollectionID"); + + b1.ToTable("collections"); + + b1.WithOwner() + .HasForeignKey("CollectionID") + .HasConstraintName("fk_collections_collections_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("CollectionID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("CollectionID"); + + b1.ToTable("collections"); + + b1.WithOwner() + .HasForeignKey("CollectionID") + .HasConstraintName("fk_collections_collections_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => + { + b.HasOne("Kyoo.Abstractions.Models.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonID") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("fk_episodes_seasons_season_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", "Show") + .WithMany("Episodes") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_episodes_shows_show_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("EpisodeID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("EpisodeID"); + + b1.ToTable("episodes"); + + b1.WithOwner() + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_episodes_episodes_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("EpisodeID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("EpisodeID"); + + b1.ToTable("episodes"); + + b1.WithOwner() + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_episodes_episodes_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("EpisodeID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("EpisodeID"); + + b1.ToTable("episodes"); + + b1.WithOwner() + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_episodes_episodes_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Season"); + + b.Navigation("Show"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("LibraryItemID") + .HasColumnType("integer") + .HasColumnName("library_item_id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("source"); + + b1.HasKey("LibraryItemID"); + + b1.ToTable((string)null); + + b1.ToView("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemID"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("LibraryItemID") + .HasColumnType("integer") + .HasColumnName("library_item_id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("source"); + + b1.HasKey("LibraryItemID"); + + b1.ToTable((string)null); + + b1.ToView("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemID"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("LibraryItemID") + .HasColumnType("integer") + .HasColumnName("library_item_id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("source"); + + b1.HasKey("LibraryItemID"); + + b1.ToTable((string)null); + + b1.ToView("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemID"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("PeopleID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("PeopleID"); + + b1.ToTable("people"); + + b1.WithOwner() + .HasForeignKey("PeopleID") + .HasConstraintName("fk_people_people_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("PeopleID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("PeopleID"); + + b1.ToTable("people"); + + b1.WithOwner() + .HasForeignKey("PeopleID") + .HasConstraintName("fk_people_people_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("PeopleID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("PeopleID"); + + b1.ToTable("people"); + + b1.WithOwner() + .HasForeignKey("PeopleID") + .HasConstraintName("fk_people_people_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => + { + b.HasOne("Kyoo.Abstractions.Models.People", "People") + .WithMany("Roles") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_people_roles_people_people_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", "Show") + .WithMany("People") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_people_roles_shows_show_id"); + + b.Navigation("People"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => + { + b.HasOne("Kyoo.Abstractions.Models.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_seasons_shows_show_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("SeasonID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("SeasonID"); + + b1.ToTable("seasons"); + + b1.WithOwner() + .HasForeignKey("SeasonID") + .HasConstraintName("fk_seasons_seasons_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("SeasonID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("SeasonID"); + + b1.ToTable("seasons"); + + b1.WithOwner() + .HasForeignKey("SeasonID") + .HasConstraintName("fk_seasons_seasons_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("SeasonID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("SeasonID"); + + b1.ToTable("seasons"); + + b1.WithOwner() + .HasForeignKey("SeasonID") + .HasConstraintName("fk_seasons_seasons_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Show"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => + { + b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") + .WithMany("Shows") + .HasForeignKey("StudioID") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_shows_studios_studio_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("ShowID"); + + b1.ToTable("shows"); + + b1.WithOwner() + .HasForeignKey("ShowID") + .HasConstraintName("fk_shows_shows_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("ShowID"); + + b1.ToTable("shows"); + + b1.WithOwner() + .HasForeignKey("ShowID") + .HasConstraintName("fk_shows_shows_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("ShowID"); + + b1.ToTable("shows"); + + b1.WithOwner() + .HasForeignKey("ShowID") + .HasConstraintName("fk_shows_shows_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Studio"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("UserID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("UserID"); + + b1.ToTable("users"); + + b1.WithOwner() + .HasForeignKey("UserID") + .HasConstraintName("fk_users_users_id"); + }); + + b.Navigation("Logo"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.WatchedEpisode", b => + { + b.HasOne("Kyoo.Abstractions.Models.Episode", "Episode") + .WithMany() + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_watched_episodes_episodes_episode_id"); + + b.HasOne("Kyoo.Abstractions.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_watched_episodes_users_user_id"); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Abstractions.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_user_show_users_users_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_user_show_shows_watched_id"); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_show_collections_collection_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_show_shows_show_id"); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_library_collection_collections_collection_id"); + + b.HasOne("Kyoo.Abstractions.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_library_collection_libraries_library_id"); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.HasOne("Kyoo.Abstractions.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_library_show_libraries_library_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_library_show_shows_show_id"); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.HasOne("Kyoo.Abstractions.Models.Genre", null) + .WithMany() + .HasForeignKey("genre_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_show_genre_genres_genre_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_show_genre_shows_show_id"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => + { + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => + { + b.Navigation("Episodes"); + + b.Navigation("People"); + + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => + { + b.Navigation("Shows"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => + { + b.Navigation("CurrentlyWatching"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.cs b/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.cs new file mode 100644 index 00000000..98a3a9af --- /dev/null +++ b/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.cs @@ -0,0 +1,772 @@ +// Kyoo - A portable and vast media library solution. +// Copyright (c) Kyoo. +// +// See AUTHORS.md and LICENSE file in the project root for full license information. +// +// Kyoo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// Kyoo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kyoo. If not, see . + +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Kyoo.Postgresql.Migrations +{ + /// + public partial class AddBlurhash : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + MigrationHelper.DropLibraryItemsView(migrationBuilder); + + migrationBuilder.DropTable( + name: "collection_metadata_id"); + + migrationBuilder.DropTable( + name: "episode_metadata_id"); + + migrationBuilder.DropTable( + name: "link_library_provider"); + + migrationBuilder.DropTable( + name: "people_metadata_id"); + + migrationBuilder.DropTable( + name: "season_metadata_id"); + + migrationBuilder.DropTable( + name: "show_metadata_id"); + + migrationBuilder.DropTable( + name: "studio_metadata_id"); + + migrationBuilder.DropTable( + name: "providers"); + + migrationBuilder.DropColumn( + name: "images", + table: "users"); + + migrationBuilder.DropColumn( + name: "images", + table: "shows"); + + migrationBuilder.DropColumn( + name: "images", + table: "seasons"); + + migrationBuilder.DropColumn( + name: "images", + table: "people"); + + migrationBuilder.DropColumn( + name: "images", + table: "episodes"); + + migrationBuilder.DropColumn( + name: "images", + table: "collections"); + + migrationBuilder.AddColumn( + name: "logo_blurhash", + table: "users", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_source", + table: "users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "external_id", + table: "studios", + type: "json", + nullable: true); + + migrationBuilder.AddColumn( + name: "external_id", + table: "shows", + type: "json", + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_blurhash", + table: "shows", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_source", + table: "shows", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_blurhash", + table: "shows", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_source", + table: "shows", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_blurhash", + table: "shows", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_source", + table: "shows", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "trailer", + table: "shows", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "external_id", + table: "seasons", + type: "json", + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_blurhash", + table: "seasons", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_source", + table: "seasons", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_blurhash", + table: "seasons", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_source", + table: "seasons", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_blurhash", + table: "seasons", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_source", + table: "seasons", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "external_id", + table: "people", + type: "json", + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_blurhash", + table: "people", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_source", + table: "people", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_blurhash", + table: "people", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_source", + table: "people", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_blurhash", + table: "people", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_source", + table: "people", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "external_id", + table: "episodes", + type: "json", + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_blurhash", + table: "episodes", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_source", + table: "episodes", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_blurhash", + table: "episodes", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_source", + table: "episodes", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_blurhash", + table: "episodes", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_source", + table: "episodes", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "external_id", + table: "collections", + type: "json", + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_blurhash", + table: "collections", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "logo_source", + table: "collections", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_blurhash", + table: "collections", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "poster_source", + table: "collections", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_blurhash", + table: "collections", + type: "character varying(32)", + maxLength: 32, + nullable: true); + + migrationBuilder.AddColumn( + name: "thumbnail_source", + table: "collections", + type: "text", + nullable: true); + + MigrationHelper.CreateLibraryItemsView(migrationBuilder); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + MigrationHelper.DropLibraryItemsView(migrationBuilder); + + migrationBuilder.DropColumn( + name: "logo_blurhash", + table: "users"); + + migrationBuilder.DropColumn( + name: "logo_source", + table: "users"); + + migrationBuilder.DropColumn( + name: "external_id", + table: "studios"); + + migrationBuilder.DropColumn( + name: "external_id", + table: "shows"); + + migrationBuilder.DropColumn( + name: "logo_blurhash", + table: "shows"); + + migrationBuilder.DropColumn( + name: "logo_source", + table: "shows"); + + migrationBuilder.DropColumn( + name: "poster_blurhash", + table: "shows"); + + migrationBuilder.DropColumn( + name: "poster_source", + table: "shows"); + + migrationBuilder.DropColumn( + name: "thumbnail_blurhash", + table: "shows"); + + migrationBuilder.DropColumn( + name: "thumbnail_source", + table: "shows"); + + migrationBuilder.DropColumn( + name: "trailer", + table: "shows"); + + migrationBuilder.DropColumn( + name: "external_id", + table: "seasons"); + + migrationBuilder.DropColumn( + name: "logo_blurhash", + table: "seasons"); + + migrationBuilder.DropColumn( + name: "logo_source", + table: "seasons"); + + migrationBuilder.DropColumn( + name: "poster_blurhash", + table: "seasons"); + + migrationBuilder.DropColumn( + name: "poster_source", + table: "seasons"); + + migrationBuilder.DropColumn( + name: "thumbnail_blurhash", + table: "seasons"); + + migrationBuilder.DropColumn( + name: "thumbnail_source", + table: "seasons"); + + migrationBuilder.DropColumn( + name: "external_id", + table: "people"); + + migrationBuilder.DropColumn( + name: "logo_blurhash", + table: "people"); + + migrationBuilder.DropColumn( + name: "logo_source", + table: "people"); + + migrationBuilder.DropColumn( + name: "poster_blurhash", + table: "people"); + + migrationBuilder.DropColumn( + name: "poster_source", + table: "people"); + + migrationBuilder.DropColumn( + name: "thumbnail_blurhash", + table: "people"); + + migrationBuilder.DropColumn( + name: "thumbnail_source", + table: "people"); + + migrationBuilder.DropColumn( + name: "external_id", + table: "episodes"); + + migrationBuilder.DropColumn( + name: "logo_blurhash", + table: "episodes"); + + migrationBuilder.DropColumn( + name: "logo_source", + table: "episodes"); + + migrationBuilder.DropColumn( + name: "poster_blurhash", + table: "episodes"); + + migrationBuilder.DropColumn( + name: "poster_source", + table: "episodes"); + + migrationBuilder.DropColumn( + name: "thumbnail_blurhash", + table: "episodes"); + + migrationBuilder.DropColumn( + name: "thumbnail_source", + table: "episodes"); + + migrationBuilder.DropColumn( + name: "external_id", + table: "collections"); + + migrationBuilder.DropColumn( + name: "logo_blurhash", + table: "collections"); + + migrationBuilder.DropColumn( + name: "logo_source", + table: "collections"); + + migrationBuilder.DropColumn( + name: "poster_blurhash", + table: "collections"); + + migrationBuilder.DropColumn( + name: "poster_source", + table: "collections"); + + migrationBuilder.DropColumn( + name: "thumbnail_blurhash", + table: "collections"); + + migrationBuilder.DropColumn( + name: "thumbnail_source", + table: "collections"); + + migrationBuilder.AddColumn>( + name: "images", + table: "users", + type: "jsonb", + nullable: true); + + migrationBuilder.AddColumn>( + name: "images", + table: "shows", + type: "jsonb", + nullable: true); + + migrationBuilder.AddColumn>( + name: "images", + table: "seasons", + type: "jsonb", + nullable: true); + + migrationBuilder.AddColumn>( + name: "images", + table: "people", + type: "jsonb", + nullable: true); + + migrationBuilder.AddColumn>( + name: "images", + table: "episodes", + type: "jsonb", + nullable: true); + + migrationBuilder.AddColumn>( + name: "images", + table: "collections", + type: "jsonb", + nullable: true); + + migrationBuilder.CreateTable( + name: "providers", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + images = table.Column>(type: "jsonb", nullable: true), + name = table.Column(type: "text", nullable: true), + slug = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_providers", x => x.id); + }); + + 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: "episode_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_episode_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_episode_metadata_id_episodes_episode_id", + column: x => x.resource_id, + principalTable: "episodes", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_episode_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 + { + 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.library_id, x.provider_id }); + table.ForeignKey( + 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_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "people_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_people_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_people_metadata_id_people_people_id", + column: x => x.resource_id, + principalTable: "people", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_people_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "season_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_season_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + name: "fk_season_metadata_id_providers_provider_id", + column: x => x.provider_id, + principalTable: "providers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_season_metadata_id_seasons_season_id", + column: x => x.resource_id, + principalTable: "seasons", + principalColumn: "id", + 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: "studio_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_studio_metadata_id", x => new { x.resource_id, x.provider_id }); + table.ForeignKey( + 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.CreateIndex( + name: "ix_collection_metadata_id_provider_id", + table: "collection_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_episode_metadata_id_provider_id", + table: "episode_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_link_library_provider_provider_id", + table: "link_library_provider", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_metadata_id_provider_id", + table: "people_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_providers_slug", + table: "providers", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_season_metadata_id_provider_id", + table: "season_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_show_metadata_id_provider_id", + table: "show_metadata_id", + column: "provider_id"); + + migrationBuilder.CreateIndex( + name: "ix_studio_metadata_id_provider_id", + table: "studio_metadata_id", + column: "provider_id"); + + MigrationHelper.CreateLibraryItemsView(migrationBuilder); + } + } +} diff --git a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index b0985624..69fc5caf 100644 --- a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -1,1186 +1,1336 @@ -// +// using System; using System.Collections.Generic; using Kyoo.Abstractions.Models; +using Kyoo.Postgresql; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable namespace Kyoo.Postgresql.Migrations { - [DbContext(typeof(PostgresContext))] - partial class PostgresContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { + [DbContext(typeof(PostgresContext))] + partial class PostgresContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "7.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + modelBuilder + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); - NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_type", new[] { "show", "movie", "collection" }); - NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_type", new[] { "show", "movie", "collection" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); - b.Property("Overview") - .HasColumnType("text") - .HasColumnName("overview"); + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); - b.HasKey("ID") - .HasName("pk_collections"); + b.HasKey("ID") + .HasName("pk_collections"); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_collections_slug"); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_collections_slug"); - b.ToTable("collections", (string)null); - }); + b.ToTable("collections", (string)null); + }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("AbsoluteNumber") - .HasColumnType("integer") - .HasColumnName("absolute_number"); + b.Property("AbsoluteNumber") + .HasColumnType("integer") + .HasColumnName("absolute_number"); - b.Property("EpisodeNumber") - .HasColumnType("integer") - .HasColumnName("episode_number"); + b.Property("EpisodeNumber") + .HasColumnType("integer") + .HasColumnName("episode_number"); - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); - b.Property("Overview") - .HasColumnType("text") - .HasColumnName("overview"); + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); - b.Property("Path") - .HasColumnType("text") - .HasColumnName("path"); + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); - b.Property("ReleaseDate") - .HasColumnType("timestamp with time zone") - .HasColumnName("release_date"); + b.Property("ReleaseDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("release_date"); - b.Property("SeasonID") - .HasColumnType("integer") - .HasColumnName("season_id"); + b.Property("SeasonID") + .HasColumnType("integer") + .HasColumnName("season_id"); - b.Property("SeasonNumber") - .HasColumnType("integer") - .HasColumnName("season_number"); + b.Property("SeasonNumber") + .HasColumnType("integer") + .HasColumnName("season_number"); - b.Property("ShowID") - .HasColumnType("integer") - .HasColumnName("show_id"); + b.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("show_id"); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); - b.HasKey("ID") - .HasName("pk_episodes"); + b.HasKey("ID") + .HasName("pk_episodes"); - b.HasIndex("SeasonID") - .HasDatabaseName("ix_episodes_season_id"); + b.HasIndex("SeasonID") + .HasDatabaseName("ix_episodes_season_id"); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_episodes_slug"); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_episodes_slug"); - b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") - .IsUnique() - .HasDatabaseName("ix_episodes_show_id_season_number_episode_number_absolute_numb"); + b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") + .IsUnique() + .HasDatabaseName("ix_episodes_show_id_season_number_episode_number_absolute_numb"); - b.ToTable("episodes", (string)null); - }); + b.ToTable("episodes", (string)null); + }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Genre", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + modelBuilder.Entity("Kyoo.Abstractions.Models.Genre", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); - b.HasKey("ID") - .HasName("pk_genres"); + b.HasKey("ID") + .HasName("pk_genres"); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_genres_slug"); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_genres_slug"); - b.ToTable("genres", (string)null); - }); + b.ToTable("genres", (string)null); + }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Library", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + modelBuilder.Entity("Kyoo.Abstractions.Models.Library", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); - b.Property("Paths") - .HasColumnType("text[]") - .HasColumnName("paths"); + b.Property("Paths") + .HasColumnType("text[]") + .HasColumnName("paths"); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); - b.HasKey("ID") - .HasName("pk_libraries"); + b.HasKey("ID") + .HasName("pk_libraries"); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_libraries_slug"); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_libraries_slug"); - b.ToTable("libraries", (string)null); - }); + b.ToTable("libraries", (string)null); + }); - modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => - { - b.Property("ID") - .HasColumnType("integer") - .HasColumnName("id"); + modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + { + b.Property("ID") + .HasColumnType("integer") + .HasColumnName("id"); - b.Property("EndAir") - .HasColumnType("timestamp with time zone") - .HasColumnName("end_air"); + b.Property("EndAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_air"); - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); - b.Property("Overview") - .HasColumnType("text") - .HasColumnName("overview"); + b.Property("Slug") + .HasColumnType("text") + .HasColumnName("slug"); - b.Property("Slug") - .HasColumnType("text") - .HasColumnName("slug"); + b.Property("StartAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_air"); - b.Property("StartAir") - .HasColumnType("timestamp with time zone") - .HasColumnName("start_air"); + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); - b.Property("Status") - .HasColumnType("status") - .HasColumnName("status"); + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); + b.Property("Type") + .HasColumnType("item_type") + .HasColumnName("type"); - b.Property("Type") - .HasColumnType("item_type") - .HasColumnName("type"); + b.HasKey("ID") + .HasName("pk_library_items"); - b.HasKey("ID") - .HasName("pk_library_items"); + b.ToTable((string)null); - b.ToTable((string)null); + b.ToView("library_items", (string)null); + }); - b.ToView("library_items", (string)null); - }); + modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.HasKey("ID") + .HasName("pk_people"); - b.HasKey("ID") - .HasName("pk_people"); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_people_slug"); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_people_slug"); + b.ToTable("people", (string)null); + }); - b.ToTable("people", (string)null); - }); + modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + b.Property("PeopleID") + .HasColumnType("integer") + .HasColumnName("people_id"); - b.Property("PeopleID") - .HasColumnType("integer") - .HasColumnName("people_id"); + b.Property("Role") + .HasColumnType("text") + .HasColumnName("role"); - b.Property("Role") - .HasColumnType("text") - .HasColumnName("role"); + b.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("show_id"); - b.Property("ShowID") - .HasColumnType("integer") - .HasColumnName("show_id"); + b.Property("Type") + .HasColumnType("text") + .HasColumnName("type"); - b.Property("Type") - .HasColumnType("text") - .HasColumnName("type"); + b.HasKey("ID") + .HasName("pk_people_roles"); - b.HasKey("ID") - .HasName("pk_people_roles"); + b.HasIndex("PeopleID") + .HasDatabaseName("ix_people_roles_people_id"); - b.HasIndex("PeopleID") - .HasDatabaseName("ix_people_roles_people_id"); + b.HasIndex("ShowID") + .HasDatabaseName("ix_people_roles_show_id"); - b.HasIndex("ShowID") - .HasDatabaseName("ix_people_roles_show_id"); + b.ToTable("people_roles", (string)null); + }); - b.ToTable("people_roles", (string)null); - }); + modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - modelBuilder.Entity("Kyoo.Abstractions.Models.Provider", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + b.Property("EndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_date"); - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.Property("SeasonNumber") + .HasColumnType("integer") + .HasColumnName("season_number"); - b.HasKey("ID") - .HasName("pk_providers"); + b.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("show_id"); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_providers_slug"); + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); - b.ToTable("providers", (string)null); - }); + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); - modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + b.HasKey("ID") + .HasName("pk_seasons"); - b.Property("EndDate") - .HasColumnType("timestamp with time zone") - .HasColumnName("end_date"); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_seasons_slug"); - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); + b.HasIndex("ShowID", "SeasonNumber") + .IsUnique() + .HasDatabaseName("ix_seasons_show_id_season_number"); - b.Property("Overview") - .HasColumnType("text") - .HasColumnName("overview"); + b.ToTable("seasons", (string)null); + }); - b.Property("SeasonNumber") - .HasColumnType("integer") - .HasColumnName("season_number"); + modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - b.Property("ShowID") - .HasColumnType("integer") - .HasColumnName("show_id"); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.Property("Aliases") + .HasColumnType("text[]") + .HasColumnName("aliases"); - b.Property("StartDate") - .HasColumnType("timestamp with time zone") - .HasColumnName("start_date"); + b.Property("EndAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_air"); - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); - b.HasKey("ID") - .HasName("pk_seasons"); + b.Property("IsMovie") + .HasColumnType("boolean") + .HasColumnName("is_movie"); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_seasons_slug"); + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); - b.HasIndex("ShowID", "SeasonNumber") - .IsUnique() - .HasDatabaseName("ix_seasons_show_id_season_number"); + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); - b.ToTable("seasons", (string)null); - }); + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); - modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + b.Property("StartAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_air"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); - b.Property("Aliases") - .HasColumnType("text[]") - .HasColumnName("aliases"); + b.Property("StudioID") + .HasColumnType("integer") + .HasColumnName("studio_id"); - b.Property("EndAir") - .HasColumnType("timestamp with time zone") - .HasColumnName("end_air"); + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); - b.Property("IsMovie") - .HasColumnType("boolean") - .HasColumnName("is_movie"); + b.HasKey("ID") + .HasName("pk_shows"); - b.Property("Overview") - .HasColumnType("text") - .HasColumnName("overview"); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_shows_slug"); - b.Property("Path") - .HasColumnType("text") - .HasColumnName("path"); + b.HasIndex("StudioID") + .HasDatabaseName("ix_shows_studio_id"); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.ToTable("shows", (string)null); + }); - b.Property("StartAir") - .HasColumnType("timestamp with time zone") - .HasColumnName("start_air"); + modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - b.Property("Status") - .HasColumnType("status") - .HasColumnName("status"); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("StudioID") - .HasColumnType("integer") - .HasColumnName("studio_id"); + b.Property("ExternalIDs") + .HasColumnType("json") + .HasColumnName("external_i_ds"); - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); - b.HasKey("ID") - .HasName("pk_shows"); + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_shows_slug"); + b.HasKey("ID") + .HasName("pk_studios"); - b.HasIndex("StudioID") - .HasDatabaseName("ix_shows_studio_id"); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_studios_slug"); - b.ToTable("shows", (string)null); - }); + b.ToTable("studios", (string)null); + }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.Property>("ExtraData") + .HasColumnType("jsonb") + .HasColumnName("extra_data"); - b.HasKey("ID") - .HasName("pk_studios"); + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_studios_slug"); + b.Property("Permissions") + .HasColumnType("text[]") + .HasColumnName("permissions"); - b.ToTable("studios", (string)null); - }); + b.Property("Slug") + .IsRequired() + .HasColumnType("text") + .HasColumnName("slug"); - modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + b.HasKey("ID") + .HasName("pk_users"); - b.Property("Email") - .HasColumnType("text") - .HasColumnName("email"); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_users_slug"); - b.Property>("ExtraData") - .HasColumnType("jsonb") - .HasColumnName("extra_data"); + b.ToTable("users", (string)null); + }); - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); + modelBuilder.Entity("Kyoo.Abstractions.Models.WatchedEpisode", b => + { + b.Property("UserID") + .HasColumnType("integer") + .HasColumnName("user_id"); - b.Property("Password") - .HasColumnType("text") - .HasColumnName("password"); + b.Property("EpisodeID") + .HasColumnType("integer") + .HasColumnName("episode_id"); - b.Property("Permissions") - .HasColumnType("text[]") - .HasColumnName("permissions"); + b.Property("WatchedPercentage") + .HasColumnType("integer") + .HasColumnName("watched_percentage"); - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); + b.HasKey("UserID", "EpisodeID") + .HasName("pk_watched_episodes"); - b.Property("Username") - .HasColumnType("text") - .HasColumnName("username"); + b.HasIndex("EpisodeID") + .HasDatabaseName("ix_watched_episodes_episode_id"); - b.HasKey("ID") - .HasName("pk_users"); + b.ToTable("watched_episodes", (string)null); + }); - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_users_slug"); + modelBuilder.Entity("ShowUser", b => + { + b.Property("UsersID") + .HasColumnType("integer") + .HasColumnName("users_id"); - b.ToTable("users", (string)null); - }); + b.Property("WatchedID") + .HasColumnType("integer") + .HasColumnName("watched_id"); - modelBuilder.Entity("Kyoo.Abstractions.Models.WatchedEpisode", b => - { - b.Property("UserID") - .HasColumnType("integer") - .HasColumnName("user_id"); + b.HasKey("UsersID", "WatchedID") + .HasName("pk_link_user_show"); - b.Property("EpisodeID") - .HasColumnType("integer") - .HasColumnName("episode_id"); + b.HasIndex("WatchedID") + .HasDatabaseName("ix_link_user_show_watched_id"); - b.Property("WatchedPercentage") - .HasColumnType("integer") - .HasColumnName("watched_percentage"); + b.ToTable("link_user_show", (string)null); + }); - b.HasKey("UserID", "EpisodeID") - .HasName("pk_watched_episodes"); + modelBuilder.Entity("link_collection_show", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); - b.HasIndex("EpisodeID") - .HasDatabaseName("ix_watched_episodes_episode_id"); + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); - b.ToTable("watched_episodes", (string)null); - }); + b.HasKey("collection_id", "show_id") + .HasName("pk_link_collection_show"); - modelBuilder.Entity("ShowUser", b => - { - b.Property("UsersID") - .HasColumnType("integer") - .HasColumnName("users_id"); + b.HasIndex("show_id") + .HasDatabaseName("ix_link_collection_show_show_id"); - b.Property("WatchedID") - .HasColumnType("integer") - .HasColumnName("watched_id"); + b.ToTable("link_collection_show", (string)null); + }); - b.HasKey("UsersID", "WatchedID") - .HasName("pk_link_user_show"); + modelBuilder.Entity("link_library_collection", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); - b.HasIndex("WatchedID") - .HasDatabaseName("ix_link_user_show_watched_id"); + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); - b.ToTable("link_user_show", (string)null); - }); + b.HasKey("collection_id", "library_id") + .HasName("pk_link_library_collection"); - modelBuilder.Entity("collection_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); + b.HasIndex("library_id") + .HasDatabaseName("ix_link_library_collection_library_id"); - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); + b.ToTable("link_library_collection", (string)null); + }); - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); + modelBuilder.Entity("link_library_show", b => + { + b.Property("library_id") + .HasColumnType("integer") + .HasColumnName("library_id"); - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_collection_metadata_id"); + b.HasKey("library_id", "show_id") + .HasName("pk_link_library_show"); - b.HasIndex("ProviderID") - .HasDatabaseName("ix_collection_metadata_id_provider_id"); + b.HasIndex("show_id") + .HasDatabaseName("ix_link_library_show_show_id"); - b.ToTable("collection_metadata_id", (string)null); - }); + b.ToTable("link_library_show", (string)null); + }); - modelBuilder.Entity("episode_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); + modelBuilder.Entity("link_show_genre", b => + { + b.Property("genre_id") + .HasColumnType("integer") + .HasColumnName("genre_id"); - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); + b.Property("show_id") + .HasColumnType("integer") + .HasColumnName("show_id"); - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); + b.HasKey("genre_id", "show_id") + .HasName("pk_link_show_genre"); - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); + b.HasIndex("show_id") + .HasDatabaseName("ix_link_show_genre_show_id"); - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_episode_metadata_id"); + b.ToTable("link_show_genre", (string)null); + }); - b.HasIndex("ProviderID") - .HasDatabaseName("ix_episode_metadata_id_provider_id"); + modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("CollectionID") + .HasColumnType("integer") + .HasColumnName("id"); - b.ToTable("episode_metadata_id", (string)null); - }); + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); - modelBuilder.Entity("link_collection_show", b => - { - b.Property("collection_id") - .HasColumnType("integer") - .HasColumnName("collection_id"); + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); - b.Property("show_id") - .HasColumnType("integer") - .HasColumnName("show_id"); + b1.HasKey("CollectionID"); - b.HasKey("collection_id", "show_id") - .HasName("pk_link_collection_show"); + b1.ToTable("collections"); - b.HasIndex("show_id") - .HasDatabaseName("ix_link_collection_show_show_id"); + b1.WithOwner() + .HasForeignKey("CollectionID") + .HasConstraintName("fk_collections_collections_id"); + }); - b.ToTable("link_collection_show", (string)null); - }); + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("CollectionID") + .HasColumnType("integer") + .HasColumnName("id"); - modelBuilder.Entity("link_library_collection", b => - { - b.Property("collection_id") - .HasColumnType("integer") - .HasColumnName("collection_id"); + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); - b.Property("library_id") - .HasColumnType("integer") - .HasColumnName("library_id"); + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); - b.HasKey("collection_id", "library_id") - .HasName("pk_link_library_collection"); + b1.HasKey("CollectionID"); - b.HasIndex("library_id") - .HasDatabaseName("ix_link_library_collection_library_id"); + b1.ToTable("collections"); - b.ToTable("link_library_collection", (string)null); - }); + b1.WithOwner() + .HasForeignKey("CollectionID") + .HasConstraintName("fk_collections_collections_id"); + }); - modelBuilder.Entity("link_library_provider", b => - { - b.Property("library_id") - .HasColumnType("integer") - .HasColumnName("library_id"); + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("CollectionID") + .HasColumnType("integer") + .HasColumnName("id"); - b.Property("provider_id") - .HasColumnType("integer") - .HasColumnName("provider_id"); + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); - b.HasKey("library_id", "provider_id") - .HasName("pk_link_library_provider"); + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); - b.HasIndex("provider_id") - .HasDatabaseName("ix_link_library_provider_provider_id"); + b1.HasKey("CollectionID"); - b.ToTable("link_library_provider", (string)null); - }); + b1.ToTable("collections"); - modelBuilder.Entity("link_library_show", b => - { - b.Property("library_id") - .HasColumnType("integer") - .HasColumnName("library_id"); + b1.WithOwner() + .HasForeignKey("CollectionID") + .HasConstraintName("fk_collections_collections_id"); + }); - b.Property("show_id") - .HasColumnType("integer") - .HasColumnName("show_id"); + b.Navigation("Logo"); - b.HasKey("library_id", "show_id") - .HasName("pk_link_library_show"); + b.Navigation("Poster"); - b.HasIndex("show_id") - .HasDatabaseName("ix_link_library_show_show_id"); + b.Navigation("Thumbnail"); + }); - b.ToTable("link_library_show", (string)null); - }); + modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => + { + b.HasOne("Kyoo.Abstractions.Models.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonID") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("fk_episodes_seasons_season_id"); - modelBuilder.Entity("link_show_genre", b => - { - b.Property("genre_id") - .HasColumnType("integer") - .HasColumnName("genre_id"); + b.HasOne("Kyoo.Abstractions.Models.Show", "Show") + .WithMany("Episodes") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_episodes_shows_show_id"); - b.Property("show_id") - .HasColumnType("integer") - .HasColumnName("show_id"); + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("EpisodeID") + .HasColumnType("integer") + .HasColumnName("id"); - b.HasKey("genre_id", "show_id") - .HasName("pk_link_show_genre"); + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); - b.HasIndex("show_id") - .HasDatabaseName("ix_link_show_genre_show_id"); + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); - b.ToTable("link_show_genre", (string)null); - }); + b1.HasKey("EpisodeID"); - modelBuilder.Entity("people_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); + b1.ToTable("episodes"); - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); + b1.WithOwner() + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_episodes_episodes_id"); + }); - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("EpisodeID") + .HasColumnType("integer") + .HasColumnName("id"); - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_people_metadata_id"); + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); - b.HasIndex("ProviderID") - .HasDatabaseName("ix_people_metadata_id_provider_id"); + b1.HasKey("EpisodeID"); - b.ToTable("people_metadata_id", (string)null); - }); + b1.ToTable("episodes"); - modelBuilder.Entity("season_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); + b1.WithOwner() + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_episodes_episodes_id"); + }); - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("EpisodeID") + .HasColumnType("integer") + .HasColumnName("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"); + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); - b.HasIndex("ProviderID") - .HasDatabaseName("ix_season_metadata_id_provider_id"); + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); - b.ToTable("season_metadata_id", (string)null); - }); - - 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", (string)null); - }); - - 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", (string)null); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => - { - b.HasOne("Kyoo.Abstractions.Models.Season", "Season") - .WithMany("Episodes") - .HasForeignKey("SeasonID") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_episodes_seasons_season_id"); - - b.HasOne("Kyoo.Abstractions.Models.Show", "Show") - .WithMany("Episodes") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_episodes_shows_show_id"); - - b.Navigation("Season"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => - { - b.HasOne("Kyoo.Abstractions.Models.People", "People") - .WithMany("Roles") - .HasForeignKey("PeopleID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_people_roles_people_people_id"); - - b.HasOne("Kyoo.Abstractions.Models.Show", "Show") - .WithMany("People") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_people_roles_shows_show_id"); - - b.Navigation("People"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => - { - b.HasOne("Kyoo.Abstractions.Models.Show", "Show") - .WithMany("Seasons") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_seasons_shows_show_id"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => - { - b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") - .WithMany("Shows") - .HasForeignKey("StudioID") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_shows_studios_studio_id"); - - b.Navigation("Studio"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.WatchedEpisode", b => - { - b.HasOne("Kyoo.Abstractions.Models.Episode", "Episode") - .WithMany() - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_watched_episodes_episodes_episode_id"); - - b.HasOne("Kyoo.Abstractions.Models.User", null) - .WithMany("CurrentlyWatching") - .HasForeignKey("UserID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_watched_episodes_users_user_id"); - - b.Navigation("Episode"); - }); - - modelBuilder.Entity("ShowUser", b => - { - b.HasOne("Kyoo.Abstractions.Models.User", null) - .WithMany() - .HasForeignKey("UsersID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_user_show_users_users_id"); - - b.HasOne("Kyoo.Abstractions.Models.Show", null) - .WithMany() - .HasForeignKey("WatchedID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_user_show_shows_watched_id"); - }); - - modelBuilder.Entity("collection_metadata_id", b => - { - b.HasOne("Kyoo.Abstractions.Models.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_collection_metadata_id_providers_provider_id"); - - b.HasOne("Kyoo.Abstractions.Models.Collection", null) - .WithMany("ExternalIDs") - .HasForeignKey("ResourceID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_collection_metadata_id_collections_collection_id"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("episode_metadata_id", b => - { - b.HasOne("Kyoo.Abstractions.Models.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_episode_metadata_id_providers_provider_id"); - - b.HasOne("Kyoo.Abstractions.Models.Episode", null) - .WithMany("ExternalIDs") - .HasForeignKey("ResourceID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_episode_metadata_id_episodes_episode_id"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("link_collection_show", b => - { - b.HasOne("Kyoo.Abstractions.Models.Collection", null) - .WithMany() - .HasForeignKey("collection_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_collection_show_collections_collection_id"); - - b.HasOne("Kyoo.Abstractions.Models.Show", null) - .WithMany() - .HasForeignKey("show_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_collection_show_shows_show_id"); - }); - - modelBuilder.Entity("link_library_collection", b => - { - b.HasOne("Kyoo.Abstractions.Models.Collection", null) - .WithMany() - .HasForeignKey("collection_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_library_collection_collections_collection_id"); - - b.HasOne("Kyoo.Abstractions.Models.Library", null) - .WithMany() - .HasForeignKey("library_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_library_collection_libraries_library_id"); - }); - - modelBuilder.Entity("link_library_provider", b => - { - b.HasOne("Kyoo.Abstractions.Models.Library", null) - .WithMany() - .HasForeignKey("library_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_library_provider_libraries_library_id"); - - b.HasOne("Kyoo.Abstractions.Models.Provider", null) - .WithMany() - .HasForeignKey("provider_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_library_provider_providers_provider_id"); - }); - - modelBuilder.Entity("link_library_show", b => - { - b.HasOne("Kyoo.Abstractions.Models.Library", null) - .WithMany() - .HasForeignKey("library_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_library_show_libraries_library_id"); - - b.HasOne("Kyoo.Abstractions.Models.Show", null) - .WithMany() - .HasForeignKey("show_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_library_show_shows_show_id"); - }); - - modelBuilder.Entity("link_show_genre", b => - { - b.HasOne("Kyoo.Abstractions.Models.Genre", null) - .WithMany() - .HasForeignKey("genre_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_show_genre_genres_genre_id"); - - b.HasOne("Kyoo.Abstractions.Models.Show", null) - .WithMany() - .HasForeignKey("show_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_link_show_genre_shows_show_id"); - }); - - modelBuilder.Entity("people_metadata_id", b => - { - b.HasOne("Kyoo.Abstractions.Models.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_people_metadata_id_providers_provider_id"); - - b.HasOne("Kyoo.Abstractions.Models.People", null) - .WithMany("ExternalIDs") - .HasForeignKey("ResourceID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_people_metadata_id_people_people_id"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("season_metadata_id", b => - { - b.HasOne("Kyoo.Abstractions.Models.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_season_metadata_id_providers_provider_id"); - - b.HasOne("Kyoo.Abstractions.Models.Season", null) - .WithMany("ExternalIDs") - .HasForeignKey("ResourceID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_season_metadata_id_seasons_season_id"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("show_metadata_id", b => - { - b.HasOne("Kyoo.Abstractions.Models.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_show_metadata_id_providers_provider_id"); - - b.HasOne("Kyoo.Abstractions.Models.Show", null) - .WithMany("ExternalIDs") - .HasForeignKey("ResourceID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_show_metadata_id_shows_show_id"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("studio_metadata_id", b => - { - b.HasOne("Kyoo.Abstractions.Models.Provider", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_studio_metadata_id_providers_provider_id"); - - b.HasOne("Kyoo.Abstractions.Models.Studio", null) - .WithMany("ExternalIDs") - .HasForeignKey("ResourceID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_studio_metadata_id_studios_studio_id"); - - b.Navigation("Provider"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => - { - b.Navigation("ExternalIDs"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => - { - b.Navigation("ExternalIDs"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Roles"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => - { - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => - { - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - - b.Navigation("People"); - - b.Navigation("Seasons"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Shows"); - }); - - modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => - { - b.Navigation("CurrentlyWatching"); - }); + b1.HasKey("EpisodeID"); + + b1.ToTable("episodes"); + + b1.WithOwner() + .HasForeignKey("EpisodeID") + .HasConstraintName("fk_episodes_episodes_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Season"); + + b.Navigation("Show"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("LibraryItemID") + .HasColumnType("integer") + .HasColumnName("library_item_id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("source"); + + b1.HasKey("LibraryItemID"); + + b1.ToTable((string)null); + + b1.ToView("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemID"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("LibraryItemID") + .HasColumnType("integer") + .HasColumnName("library_item_id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("source"); + + b1.HasKey("LibraryItemID"); + + b1.ToTable((string)null); + + b1.ToView("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemID"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("LibraryItemID") + .HasColumnType("integer") + .HasColumnName("library_item_id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("source"); + + b1.HasKey("LibraryItemID"); + + b1.ToTable((string)null); + + b1.ToView("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemID"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("PeopleID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("PeopleID"); + + b1.ToTable("people"); + + b1.WithOwner() + .HasForeignKey("PeopleID") + .HasConstraintName("fk_people_people_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("PeopleID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("PeopleID"); + + b1.ToTable("people"); + + b1.WithOwner() + .HasForeignKey("PeopleID") + .HasConstraintName("fk_people_people_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("PeopleID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("PeopleID"); + + b1.ToTable("people"); + + b1.WithOwner() + .HasForeignKey("PeopleID") + .HasConstraintName("fk_people_people_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => + { + b.HasOne("Kyoo.Abstractions.Models.People", "People") + .WithMany("Roles") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_people_roles_people_people_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", "Show") + .WithMany("People") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_people_roles_shows_show_id"); + + b.Navigation("People"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => + { + b.HasOne("Kyoo.Abstractions.Models.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_seasons_shows_show_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("SeasonID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("SeasonID"); + + b1.ToTable("seasons"); + + b1.WithOwner() + .HasForeignKey("SeasonID") + .HasConstraintName("fk_seasons_seasons_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("SeasonID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("SeasonID"); + + b1.ToTable("seasons"); + + b1.WithOwner() + .HasForeignKey("SeasonID") + .HasConstraintName("fk_seasons_seasons_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("SeasonID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("SeasonID"); + + b1.ToTable("seasons"); + + b1.WithOwner() + .HasForeignKey("SeasonID") + .HasConstraintName("fk_seasons_seasons_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Show"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => + { + b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") + .WithMany("Shows") + .HasForeignKey("StudioID") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_shows_studios_studio_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("ShowID"); + + b1.ToTable("shows"); + + b1.WithOwner() + .HasForeignKey("ShowID") + .HasConstraintName("fk_shows_shows_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("ShowID"); + + b1.ToTable("shows"); + + b1.WithOwner() + .HasForeignKey("ShowID") + .HasConstraintName("fk_shows_shows_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("ShowID"); + + b1.ToTable("shows"); + + b1.WithOwner() + .HasForeignKey("ShowID") + .HasConstraintName("fk_shows_shows_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Studio"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("UserID") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("UserID"); + + b1.ToTable("users"); + + b1.WithOwner() + .HasForeignKey("UserID") + .HasConstraintName("fk_users_users_id"); + }); + + b.Navigation("Logo"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.WatchedEpisode", b => + { + b.HasOne("Kyoo.Abstractions.Models.Episode", "Episode") + .WithMany() + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_watched_episodes_episodes_episode_id"); + + b.HasOne("Kyoo.Abstractions.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_watched_episodes_users_user_id"); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("ShowUser", b => + { + b.HasOne("Kyoo.Abstractions.Models.User", null) + .WithMany() + .HasForeignKey("UsersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_user_show_users_users_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", null) + .WithMany() + .HasForeignKey("WatchedID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_user_show_shows_watched_id"); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_show_collections_collection_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_show_shows_show_id"); + }); + + modelBuilder.Entity("link_library_collection", b => + { + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_library_collection_collections_collection_id"); + + b.HasOne("Kyoo.Abstractions.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_library_collection_libraries_library_id"); + }); + + modelBuilder.Entity("link_library_show", b => + { + b.HasOne("Kyoo.Abstractions.Models.Library", null) + .WithMany() + .HasForeignKey("library_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_library_show_libraries_library_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_library_show_shows_show_id"); + }); + + modelBuilder.Entity("link_show_genre", b => + { + b.HasOne("Kyoo.Abstractions.Models.Genre", null) + .WithMany() + .HasForeignKey("genre_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_show_genre_genres_genre_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_show_genre_shows_show_id"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => + { + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => + { + b.Navigation("Episodes"); + + b.Navigation("People"); + + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => + { + b.Navigation("Shows"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => + { + b.Navigation("CurrentlyWatching"); + }); #pragma warning restore 612, 618 - } - } + } + } } diff --git a/back/src/Kyoo.Postgresql/PostgresContext.cs b/back/src/Kyoo.Postgresql/PostgresContext.cs index 8266fa1f..cff6167d 100644 --- a/back/src/Kyoo.Postgresql/PostgresContext.cs +++ b/back/src/Kyoo.Postgresql/PostgresContext.cs @@ -33,11 +33,6 @@ namespace Kyoo.Postgresql /// public class PostgresContext : DatabaseContext { - /// - /// The connection string to use. - /// - private readonly string _connection; - /// /// Is this instance in debug mode? /// @@ -78,7 +73,6 @@ namespace Kyoo.Postgresql /// Is this instance in debug mode? public PostgresContext(string connection, bool debugMode) { - _connection = connection; _debugMode = debugMode; } @@ -116,31 +110,6 @@ 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); } diff --git a/back/src/Kyoo.Swagger/SwaggerModule.cs b/back/src/Kyoo.Swagger/SwaggerModule.cs index 886da471..c3e9ffa4 100644 --- a/back/src/Kyoo.Swagger/SwaggerModule.cs +++ b/back/src/Kyoo.Swagger/SwaggerModule.cs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; using System.Reflection; using Kyoo.Abstractions.Controllers; @@ -87,7 +86,6 @@ namespace Kyoo.Swagger x.IsNullableRaw = false; x.Type = JsonObjectType.String | JsonObjectType.Integer; })); - document.SchemaProcessors.Add(new ThumbnailProcessor()); document.AddSecurity(nameof(Kyoo), new OpenApiSecurityScheme { diff --git a/back/src/Kyoo.Swagger/ThumbnailProcessor.cs b/back/src/Kyoo.Swagger/ThumbnailProcessor.cs deleted file mode 100644 index 591c8cd7..00000000 --- a/back/src/Kyoo.Swagger/ThumbnailProcessor.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Kyoo - A portable and vast media library solution. -// Copyright (c) Kyoo. -// -// See AUTHORS.md and LICENSE file in the project root for full license information. -// -// Kyoo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// Kyoo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using Kyoo.Abstractions.Models; -using NJsonSchema; -using NJsonSchema.Generation; - -namespace Kyoo.Swagger -{ - /// - /// An operation processor to add computed fields of . - /// - public class ThumbnailProcessor : ISchemaProcessor - { - /// - public void Process(SchemaProcessorContext context) - { - if (!context.ContextualType.OriginalType.IsAssignableTo(typeof(IThumbnails))) - return; - foreach ((int _, string imageP) in Images.ImageName) - { - string image = imageP.ToLower()[0] + imageP[1..]; - context.Schema.Properties.Add(image, new JsonSchemaProperty - { - Type = JsonObjectType.String, - IsNullableRaw = true, - Description = $"An url to the {image} of this resource. If this resource does not have an image, " + - $"the link will be null. If the kyoo's instance is not capable of handling this kind of image " + - $"for the specific resource, this field won't be present." - }); - } - } - } -} diff --git a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs index e320fd52..476dc0b6 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs @@ -37,22 +37,20 @@ namespace Kyoo.Tests.Database { Context = new PostgresTestContext(postgres, output); - ProviderRepository provider = new(_NewContext()); - LibraryRepository library = new(_NewContext(), provider); - CollectionRepository collection = new(_NewContext(), provider); + LibraryRepository library = new(_NewContext()); + CollectionRepository collection = new(_NewContext()); GenreRepository genre = new(_NewContext()); - StudioRepository studio = new(_NewContext(), provider); - PeopleRepository people = new(_NewContext(), provider, + StudioRepository studio = new(_NewContext()); + PeopleRepository people = new(_NewContext(), new Lazy(() => LibraryManager.ShowRepository)); - ShowRepository show = new(_NewContext(), studio, people, genre, provider); - SeasonRepository season = new(_NewContext(), show, provider); + ShowRepository show = new(_NewContext(), studio, people, genre); + SeasonRepository season = new(_NewContext(), show); LibraryItemRepository libraryItem = new(_NewContext(), new Lazy(() => LibraryManager.LibraryRepository)); - EpisodeRepository episode = new(_NewContext(), show, provider); + EpisodeRepository episode = new(_NewContext(), show); UserRepository user = new(_NewContext()); LibraryManager = new LibraryManager(new IBaseRepository[] { - provider, library, libraryItem, collection, diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs index 1d6bdd9f..8ecb9bae 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -78,7 +78,7 @@ namespace Kyoo.Tests.Database public async Task CreateWithExternalIdTest() { Collection collection = TestSample.GetNew(); - collection.ExternalIDs = new[] + collection.ExternalId = new[] { new MetadataID { @@ -96,10 +96,10 @@ namespace Kyoo.Tests.Database 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()); + await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId); + Assert.Equal(2, retrieved.ExternalId.Count); + KAssert.DeepEqual(collection.ExternalId.First(), retrieved.ExternalId.First()); + KAssert.DeepEqual(collection.ExternalId.Last(), retrieved.ExternalId.Last()); } [Fact] @@ -123,7 +123,7 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { Collection value = await _repository.Get(TestSample.Get().Slug); - value.ExternalIDs = new[] + value.ExternalId = new[] { new MetadataID { @@ -136,7 +136,7 @@ namespace Kyoo.Tests.Database await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); @@ -147,7 +147,7 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Collection value = await _repository.Get(TestSample.Get().Slug); - value.ExternalIDs = new List + value.ExternalId = new List { new() { @@ -161,14 +161,14 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalIDs.Add(new MetadataID + value.ExternalId.Add(new MetadataID { Provider = TestSample.GetNew(), Link = "link", @@ -179,7 +179,7 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index 22b1befb..1fc78b23 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -206,7 +206,7 @@ namespace Kyoo.Tests.Database public async Task CreateWithExternalIdTest() { Episode value = TestSample.GetNew(); - value.ExternalIDs = new[] + value.ExternalId = new[] { new MetadataID { @@ -224,10 +224,10 @@ namespace Kyoo.Tests.Database 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()); + await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId); + Assert.Equal(2, retrieved.ExternalId.Count); + KAssert.DeepEqual(value.ExternalId.First(), retrieved.ExternalId.First()); + KAssert.DeepEqual(value.ExternalId.Last(), retrieved.ExternalId.Last()); } [Fact] @@ -251,7 +251,7 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { Episode value = await _repository.Get(TestSample.Get().Slug); - value.ExternalIDs = new[] + value.ExternalId = new[] { new MetadataID { @@ -264,7 +264,7 @@ namespace Kyoo.Tests.Database await using DatabaseContext database = Repositories.Context.New(); Episode retrieved = await database.Episodes - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); @@ -275,7 +275,7 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Episode value = await _repository.Get(TestSample.Get().Slug); - value.ExternalIDs = new List + value.ExternalId = new List { new() { @@ -289,14 +289,14 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); Episode retrieved = await database.Episodes - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalIDs.Add(new MetadataID + value.ExternalId.Add(new MetadataID { Provider = TestSample.GetNew(), Link = "link", @@ -307,7 +307,7 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); Episode retrieved = await database.Episodes - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs index 50dbf151..bd6a3e70 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs @@ -52,7 +52,7 @@ namespace Kyoo.Tests.Database public async Task CreateWithExternalIdTest() { People value = TestSample.GetNew(); - value.ExternalIDs = new[] + value.ExternalId = new[] { new MetadataID { @@ -70,10 +70,10 @@ namespace Kyoo.Tests.Database 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()); + await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId); + Assert.Equal(2, retrieved.ExternalId.Count); + KAssert.DeepEqual(value.ExternalId.First(), retrieved.ExternalId.First()); + KAssert.DeepEqual(value.ExternalId.Last(), retrieved.ExternalId.Last()); } [Fact] @@ -97,7 +97,7 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { People value = await _repository.Get(TestSample.Get().Slug); - value.ExternalIDs = new[] + value.ExternalId = new[] { new MetadataID { @@ -110,7 +110,7 @@ namespace Kyoo.Tests.Database await using DatabaseContext database = Repositories.Context.New(); People retrieved = await database.People - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); @@ -121,7 +121,7 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { People value = await _repository.Get(TestSample.Get().Slug); - value.ExternalIDs = new List + value.ExternalId = new List { new() { @@ -135,14 +135,14 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); People retrieved = await database.People - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalIDs.Add(new MetadataID + value.ExternalId.Add(new MetadataID { Provider = TestSample.GetNew(), Link = "link", @@ -153,7 +153,7 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); People retrieved = await database.People - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index c5017e9a..7a336183 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -93,7 +93,7 @@ namespace Kyoo.Tests.Database public async Task CreateWithExternalIdTest() { Season season = TestSample.GetNew(); - season.ExternalIDs = new[] + season.ExternalId = new[] { new MetadataID { @@ -111,10 +111,10 @@ namespace Kyoo.Tests.Database 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()); + await Repositories.LibraryManager.Load(retrieved, x => x.ExternalId); + Assert.Equal(2, retrieved.ExternalId.Count); + KAssert.DeepEqual(season.ExternalId.First(), retrieved.ExternalId.First()); + KAssert.DeepEqual(season.ExternalId.Last(), retrieved.ExternalId.Last()); } [Fact] @@ -138,7 +138,7 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { Season value = await _repository.Get(TestSample.Get().Slug); - value.ExternalIDs = new[] + value.ExternalId = new[] { new MetadataID { @@ -151,7 +151,7 @@ namespace Kyoo.Tests.Database await using DatabaseContext database = Repositories.Context.New(); Season retrieved = await database.Seasons - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); @@ -162,7 +162,7 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Season value = await _repository.Get(TestSample.Get().Slug); - value.ExternalIDs = new List + value.ExternalId = new List { new() { @@ -176,14 +176,14 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); Season retrieved = await database.Seasons - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalIDs.Add(new MetadataID + value.ExternalId.Add(new MetadataID { Provider = TestSample.GetNew(), Link = "link", @@ -194,7 +194,7 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); Season retrieved = await database.Seasons - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index 5d24ff39..77895d8f 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -180,7 +180,7 @@ namespace Kyoo.Tests.Database public async Task EditExternalIDsTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.ExternalIDs = new[] + value.ExternalId = new[] { new MetadataID { @@ -192,19 +192,19 @@ namespace Kyoo.Tests.Database Assert.Equal(value.Slug, edited.Slug); Assert.Equal( - value.ExternalIDs.Select(x => new { x.DataID, x.Provider.Slug }), - edited.ExternalIDs.Select(x => new { x.DataID, x.Provider.Slug })); + value.ExternalId.Select(x => new { x.DataID, x.Provider.Slug }), + edited.ExternalId.Select(x => new { x.DataID, x.Provider.Slug })); await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(); Assert.Equal(value.Slug, show.Slug); Assert.Equal( - value.ExternalIDs.Select(x => new { x.DataID, x.Provider.Slug }), - show.ExternalIDs.Select(x => new { x.DataID, x.Provider.Slug })); + value.ExternalId.Select(x => new { x.DataID, x.Provider.Slug }), + show.ExternalId.Select(x => new { x.DataID, x.Provider.Slug })); } [Fact] @@ -225,7 +225,7 @@ namespace Kyoo.Tests.Database Assert.Equal("reset", edited.Slug); Assert.Equal("Reset", edited.Title); Assert.Null(edited.Aliases); - Assert.Null(edited.ExternalIDs); + Assert.Null(edited.ExternalId); Assert.Null(edited.People); Assert.Null(edited.Genres); Assert.Null(edited.Studio); @@ -237,7 +237,7 @@ namespace Kyoo.Tests.Database Show expected = TestSample.Get(); expected.ID = 0; expected.Slug = "created-relation-test"; - expected.ExternalIDs = new[] + expected.ExternalId = new[] { new MetadataID { @@ -269,7 +269,7 @@ namespace Kyoo.Tests.Database await using DatabaseContext context = Repositories.Context.New(); Show retrieved = await context.Shows - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .Include(x => x.Genres) .Include(x => x.People) @@ -300,7 +300,7 @@ namespace Kyoo.Tests.Database Show expected = TestSample.Get(); expected.ID = 0; expected.Slug = "created-relation-test"; - expected.ExternalIDs = new[] + expected.ExternalId = new[] { new MetadataID { @@ -312,12 +312,12 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(expected, created); await using DatabaseContext context = Repositories.Context.New(); Show retrieved = await context.Shows - .Include(x => x.ExternalIDs) + .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) .FirstAsync(x => x.ID == created.ID); KAssert.DeepEqual(expected, retrieved); - Assert.Single(retrieved.ExternalIDs); - Assert.Equal("ID", retrieved.ExternalIDs.First().DataID); + Assert.Single(retrieved.ExternalId); + Assert.Equal("ID", retrieved.ExternalId.First().DataID); } [Fact] diff --git a/back/tests/Kyoo.Tests/Database/TestSample.cs b/back/tests/Kyoo.Tests/Database/TestSample.cs index 709b44b5..0971de68 100644 --- a/back/tests/Kyoo.Tests/Database/TestSample.cs +++ b/back/tests/Kyoo.Tests/Database/TestSample.cs @@ -350,14 +350,9 @@ namespace Kyoo.Tests people.ID = 0; context.People.Add(people); - Provider provider = Get(); - provider.ID = 0; - context.Providers.Add(provider); - Library library = Get(); library.ID = 0; library.Collections = new List { collection }; - library.Providers = new List { provider }; context.Libraries.Add(library); User user = Get(); diff --git a/back/tests/Kyoo.Tests/Utility/MergerTests.cs b/back/tests/Kyoo.Tests/Utility/MergerTests.cs index 810e68c9..2712cacf 100644 --- a/back/tests/Kyoo.Tests/Utility/MergerTests.cs +++ b/back/tests/Kyoo.Tests/Utility/MergerTests.cs @@ -353,69 +353,54 @@ namespace Kyoo.Tests.Utility { 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() + Dictionary first = new() { - [Images.Logo] = "logo", - [Images.Poster] = "poster" + ["logo"] = "logo", + ["poster"] = "poster" }; - Dictionary second = new() + Dictionary second = new() { - [Images.Poster] = "new-poster", - [Images.Thumbnail] = "thumbnails" + ["poster"] = "new-poster", + ["thumbnail"] = "thumbnails" }; - IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); + 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]); + Assert.Equal("new-poster", ret["poster"]); + Assert.Equal("thumbnails", ret["thumbnail"]); + Assert.Equal("logo", ret["logo"]); } [Fact] public void CompleteDictionaryEqualTest() { - Dictionary first = new() + Dictionary first = new() { - [Images.Poster] = "poster" + ["poster"] = "poster" }; - Dictionary second = new() + Dictionary second = new() { - [Images.Poster] = "new-poster", + ["poster"] = "new-poster", }; - IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); + IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); Assert.True(changed); Assert.Single(ret); - Assert.Equal("new-poster", ret[Images.Poster]); + Assert.Equal("new-poster", ret["poster"]); } private class TestMergeSetter @@ -473,81 +458,81 @@ namespace Kyoo.Tests.Utility [Fact] public void MergeDictionaryNullValue() { - Dictionary first = new() + Dictionary first = new() { - [Images.Logo] = "logo", - [Images.Poster] = null + ["logo"] = "logo", + ["poster"] = null }; - Dictionary second = new() + Dictionary second = new() { - [Images.Poster] = "new-poster", - [Images.Thumbnail] = "thumbnails" + ["poster"] = "new-poster", + ["thumbnail"] = "thumbnails" }; - IDictionary ret = Merger.MergeDictionaries(first, second, out bool changed); + IDictionary ret = Merger.MergeDictionaries(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]); + Assert.Equal("new-poster", ret["poster"]); + Assert.Equal("thumbnails", ret["thumbnail"]); + Assert.Equal("logo", ret["logo"]); } [Fact] public void MergeDictionaryNullValueNoChange() { - Dictionary first = new() + Dictionary first = new() { - [Images.Logo] = "logo", - [Images.Poster] = null + ["logo"] = "logo", + ["poster"] = null }; - Dictionary second = new() + Dictionary second = new() { - [Images.Poster] = null, + ["poster"] = null, }; - IDictionary ret = Merger.MergeDictionaries(first, second, out bool changed); + IDictionary ret = Merger.MergeDictionaries(first, second, out bool changed); Assert.False(changed); Assert.Equal(2, ret.Count); - Assert.Null(ret[Images.Poster]); - Assert.Equal("logo", ret[Images.Logo]); + Assert.Null(ret["poster"]); + Assert.Equal("logo", ret["logo"]); } [Fact] public void CompleteDictionaryNullValue() { - Dictionary first = new() + Dictionary first = new() { - [Images.Logo] = "logo", - [Images.Poster] = null + ["logo"] = "logo", + ["poster"] = null }; - Dictionary second = new() + Dictionary second = new() { - [Images.Poster] = "new-poster", - [Images.Thumbnail] = "thumbnails" + ["poster"] = "new-poster", + ["thumbnail"] = "thumbnails" }; - IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); + 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]); + Assert.Equal("new-poster", ret["poster"]); + Assert.Equal("thumbnails", ret["thumbnail"]); + Assert.Equal("logo", ret["logo"]); } [Fact] public void CompleteDictionaryNullValueNoChange() { - Dictionary first = new() + Dictionary first = new() { - [Images.Logo] = "logo", - [Images.Poster] = null + ["logo"] = "logo", + ["poster"] = null }; - Dictionary second = new() + Dictionary second = new() { - [Images.Poster] = null, + ["poster"] = null, }; - IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); + IDictionary ret = Merger.CompleteDictionaries(first, second, out bool changed); Assert.False(changed); Assert.Equal(2, ret.Count); - Assert.Null(ret[Images.Poster]); - Assert.Equal("logo", ret[Images.Logo]); + Assert.Null(ret["poster"]); + Assert.Equal("logo", ret["logo"]); } } } diff --git a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs index ab7805f6..8662c4ed 100644 --- a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs +++ b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs @@ -39,7 +39,7 @@ namespace Kyoo.Tests.Utility Assert.True(KUtility.IsPropertyExpression(member)); Assert.True(KUtility.IsPropertyExpression(memberCast)); - Expression> call = x => x.GetID("test"); + Expression> call = x => x.ToString(); Assert.False(KUtility.IsPropertyExpression(call)); } diff --git a/front/packages/primitives/src/menu.tsx b/front/packages/primitives/src/menu.tsx index f00e4c81..5dbe121c 100644 --- a/front/packages/primitives/src/menu.tsx +++ b/front/packages/primitives/src/menu.tsx @@ -102,7 +102,7 @@ const Menu = ({ }), ])} > - +