From 386c6bf26834f859f50d37c035f01958e4cc3b48 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 5 Aug 2023 00:22:27 +0900 Subject: [PATCH 01/37] Feat rework images, delete providers --- .../Controllers/ILibraryManager.cs | 5 - .../Controllers/IRepository.cs | 19 - .../Controllers/IThumbnailsManager.cs | 10 +- .../Kyoo.Abstractions/Models/LibraryItem.cs | 24 +- .../Kyoo.Abstractions/Models/MetadataID.cs | 28 - .../Models/Resources/Collection.cs | 10 +- .../Models/Resources/Episode.cs | 14 +- .../Models/Resources/Interfaces/IMetadata.cs | 67 +- .../Resources/Interfaces/IThumbnails.cs | 64 +- .../Models/Resources/Library.cs | 5 - .../Models/Resources/People.cs | 10 +- .../Models/Resources/Provider.cs | 71 - .../Models/Resources/Season.cs | 10 +- .../Models/Resources/Show.cs | 26 +- .../Models/Resources/Studio.cs | 2 +- .../Models/Resources/User.cs | 8 +- .../src/Kyoo.Abstractions/Models/WatchItem.cs | 12 +- .../Kyoo.Core/Controllers/LibraryManager.cs | 45 - .../Repositories/CollectionRepository.cs | 34 +- .../Repositories/EpisodeRepository.cs | 34 +- .../Repositories/LibraryRepository.cs | 32 +- .../Repositories/LocalRepository.cs | 2 + .../Repositories/PeopleRepository.cs | 26 - .../Repositories/ProviderRepository.cs | 98 - .../Repositories/SeasonRepository.cs | 33 +- .../Repositories/ShowRepository.cs | 27 +- .../Repositories/StudioRepository.cs | 32 +- .../Controllers/ThumbnailsManager.cs | 90 +- back/src/Kyoo.Core/CoreModule.cs | 1 - back/src/Kyoo.Core/Kyoo.Core.csproj | 2 + .../Kyoo.Core/Views/Helper/CrudThumbsApi.cs | 59 +- .../Serializers/JsonSerializerContract.cs | 65 +- back/src/Kyoo.Postgresql/DatabaseContext.cs | 63 +- .../20230804143919_AddBlurhash.Designer.cs | 1339 +++++++++++ .../Migrations/20230804143919_AddBlurhash.cs | 772 ++++++ .../PostgresContextModelSnapshot.cs | 2112 +++++++++-------- back/src/Kyoo.Postgresql/PostgresContext.cs | 31 - back/src/Kyoo.Swagger/SwaggerModule.cs | 2 - back/src/Kyoo.Swagger/ThumbnailProcessor.cs | 49 - .../Database/RepositoryActivator.cs | 16 +- .../SpecificTests/CollectionsTests.cs | 22 +- .../Database/SpecificTests/EpisodeTests.cs | 22 +- .../Database/SpecificTests/PeopleTests.cs | 22 +- .../Database/SpecificTests/SeasonTests.cs | 22 +- .../Database/SpecificTests/ShowTests.cs | 26 +- back/tests/Kyoo.Tests/Database/TestSample.cs | 5 - back/tests/Kyoo.Tests/Utility/MergerTests.cs | 119 +- back/tests/Kyoo.Tests/Utility/UtilityTests.cs | 2 +- front/packages/primitives/src/menu.tsx | 2 +- scanner/providers/types/episode.py | 2 - 50 files changed, 3601 insertions(+), 1992 deletions(-) delete mode 100644 back/src/Kyoo.Abstractions/Models/Resources/Provider.cs delete mode 100644 back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs create mode 100644 back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.Designer.cs create mode 100644 back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.cs delete mode 100644 back/src/Kyoo.Swagger/ThumbnailProcessor.cs 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 = ({ }), ])} > - + Date: Sat, 5 Aug 2023 14:13:49 +0900 Subject: [PATCH 02/37] Split movies and shows, enable nullable handling (wip) --- back/Kyoo.ruleset | 3 +- .../Controllers/ILibraryManager.cs | 134 +- .../Controllers/IRepository.cs | 116 +- .../Kyoo.Abstractions.csproj | 1 + .../Attributes/ApiDefinitionAttribute.cs | 4 +- .../Models/ConfigurationReference.cs | 2 +- .../Exceptions/DuplicatedItemException.cs | 2 +- .../Interfaces/ILink.cs => Genre.cs} | 26 +- .../Kyoo.Abstractions/Models/LibraryItem.cs | 178 +-- .../Kyoo.Abstractions/Models/PeopleRole.cs | 8 +- .../Models/Resources/Collection.cs | 36 +- .../Models/Resources/Episode.cs | 30 +- .../Models/Resources/Genre.cs | 62 - .../Models/Resources/Interfaces/IResource.cs | 2 + .../Resources/Interfaces/IThumbnails.cs | 6 +- .../Models/Resources/Movie.cs | 140 ++ .../Models/Resources/People.cs | 23 +- .../Models/Resources/Season.cs | 22 +- .../Models/Resources/Show.cs | 76 +- .../Models/Resources/Studio.cs | 13 +- .../Models/Resources/User.cs | 25 +- .../Models/Resources/WatchedEpisode.cs | 2 +- .../Models/Utils/Identifier.cs | 2 +- .../Models/Utils/RequestError.cs | 6 +- .../src/Kyoo.Abstractions/Models/WatchItem.cs | 194 --- .../Utility/EnumerableExtensions.cs | 26 +- back/src/Kyoo.Abstractions/Utility/Merger.cs | 54 +- .../Kyoo.Abstractions/Utility/TaskUtils.cs | 3 +- back/src/Kyoo.Abstractions/Utility/Utility.cs | 156 +- .../Models/DTO/RegisterRequest.cs | 1 - .../Kyoo.Core/Controllers/LibraryManager.cs | 73 +- .../Repositories/EpisodeRepository.cs | 2 +- .../Repositories/GenreRepository.cs | 93 -- .../Repositories/LibraryItemRepository.cs | 108 +- .../Repositories/LibraryRepository.cs | 99 -- .../Repositories/MovieRepository.cs | 139 ++ .../Repositories/SeasonRepository.cs | 2 +- .../Repositories/ShowRepository.cs | 54 +- .../Controllers/ThumbnailsManager.cs | 6 +- back/src/Kyoo.Core/CoreModule.cs | 5 +- .../Helper/Serializers/ImageConvertor.cs} | 47 +- .../Serializers/JsonSerializerContract.cs | 1 - back/src/Kyoo.Core/Views/Metadata/GenreApi.cs | 95 -- .../Views/Resources/CollectionApi.cs | 35 - .../Kyoo.Core/Views/Resources/LibraryApi.cs | 171 --- .../Views/Resources/LibraryItemApi.cs | 11 +- .../src/Kyoo.Core/Views/Resources/MovieApi.cs | 149 ++ .../Kyoo.Core/Views/Resources/SearchApi.cs | 23 +- back/src/Kyoo.Core/Views/Resources/ShowApi.cs | 91 -- back/src/Kyoo.Core/Views/Watch/WatchApi.cs | 91 -- back/src/Kyoo.Postgresql/DatabaseContext.cs | 87 +- .../Kyoo.Postgresql/Kyoo.Postgresql.csproj | 4 + .../Migrations/20210801171613_Initial.cs | 872 ----------- .../Migrations/20210801171641_Triggers.cs | 192 --- .../20230724144449_RemoveTriggers.cs | 114 -- .../20230726100747_Timestamp.Designer.cs | 1273 ----------------- .../Migrations/20230726100747_Timestamp.cs | 138 -- .../20230731065523_RemoveTracks.Designer.cs | 1189 --------------- .../Migrations/20230731065523_RemoveTracks.cs | 95 -- .../Migrations/20230804143919_AddBlurhash.cs | 772 ---------- ....cs => 20230805051120_initial.Designer.cs} | 508 ++++--- .../Migrations/20230805051120_initial.cs | 528 +++++++ .../PostgresContextModelSnapshot.cs | 504 ++++--- back/src/Kyoo.Postgresql/PostgresContext.cs | 21 +- .../Database/RepositoryActivator.cs | 9 +- .../Database/SpecificTests/EpisodeTests.cs | 4 +- .../Database/SpecificTests/SeasonTests.cs | 4 +- .../Database/SpecificTests/ShowTests.cs | 8 +- back/tests/Kyoo.Tests/Database/TestSample.cs | 26 +- .../implementations/themoviedatabase.py | 10 +- scanner/providers/types/episode.py | 8 +- scanner/providers/types/genre.py | 4 +- scanner/providers/types/movie.py | 20 +- scanner/providers/types/season.py | 10 +- scanner/providers/types/show.py | 17 +- scanner/providers/types/studio.py | 6 +- scanner/scanner/scanner.py | 22 +- shell.nix | 1 + 78 files changed, 1976 insertions(+), 7118 deletions(-) rename back/src/Kyoo.Abstractions/Models/{Resources/Interfaces/ILink.cs => Genre.cs} (75%) delete mode 100644 back/src/Kyoo.Abstractions/Models/Resources/Genre.cs create mode 100644 back/src/Kyoo.Abstractions/Models/Resources/Movie.cs delete mode 100644 back/src/Kyoo.Abstractions/Models/WatchItem.cs delete mode 100644 back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs delete mode 100644 back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs create mode 100644 back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs rename back/src/{Kyoo.Abstractions/Models/Resources/Library.cs => Kyoo.Core/Views/Helper/Serializers/ImageConvertor.cs} (50%) delete mode 100644 back/src/Kyoo.Core/Views/Metadata/GenreApi.cs delete mode 100644 back/src/Kyoo.Core/Views/Resources/LibraryApi.cs create mode 100644 back/src/Kyoo.Core/Views/Resources/MovieApi.cs delete mode 100644 back/src/Kyoo.Core/Views/Watch/WatchApi.cs delete mode 100644 back/src/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs delete mode 100644 back/src/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs delete mode 100644 back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.cs delete mode 100644 back/src/Kyoo.Postgresql/Migrations/20230726100747_Timestamp.Designer.cs delete mode 100644 back/src/Kyoo.Postgresql/Migrations/20230726100747_Timestamp.cs delete mode 100644 back/src/Kyoo.Postgresql/Migrations/20230731065523_RemoveTracks.Designer.cs delete mode 100644 back/src/Kyoo.Postgresql/Migrations/20230731065523_RemoveTracks.cs delete mode 100644 back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.cs rename back/src/Kyoo.Postgresql/Migrations/{20230804143919_AddBlurhash.Designer.cs => 20230805051120_initial.Designer.cs} (80%) create mode 100644 back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.cs diff --git a/back/Kyoo.ruleset b/back/Kyoo.ruleset index 13d29e23..9a9c6d14 100644 --- a/back/Kyoo.ruleset +++ b/back/Kyoo.ruleset @@ -33,10 +33,11 @@ + + - diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs index 194fc2b4..646f44ca 100644 --- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; -using JetBrains.Annotations; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Exceptions; @@ -40,11 +39,6 @@ namespace Kyoo.Abstractions.Controllers IRepository GetRepository() where T : class, IResource; - /// - /// The repository that handle libraries. - /// - ILibraryRepository LibraryRepository { get; } - /// /// The repository that handle libraries items (a wrapper around shows and collections). /// @@ -55,6 +49,11 @@ namespace Kyoo.Abstractions.Controllers /// ICollectionRepository CollectionRepository { get; } + /// + /// The repository that handle shows. + /// + IMovieRepository MovieRepository { get; } + /// /// The repository that handle shows. /// @@ -80,11 +79,6 @@ namespace Kyoo.Abstractions.Controllers /// IStudioRepository StudioRepository { get; } - /// - /// The repository that handle genres. - /// - IGenreRepository GenreRepository { get; } - /// /// The repository that handle users. /// @@ -97,7 +91,6 @@ namespace Kyoo.Abstractions.Controllers /// The type of the resource /// If the item is not found /// The resource found - [ItemNotNull] Task Get(int id) where T : class, IResource; @@ -108,7 +101,6 @@ namespace Kyoo.Abstractions.Controllers /// The type of the resource /// If the item is not found /// The resource found - [ItemNotNull] Task Get(string slug) where T : class, IResource; @@ -119,7 +111,6 @@ namespace Kyoo.Abstractions.Controllers /// The type of the resource /// If the item is not found /// The first resource found that match the where function - [ItemNotNull] Task Get(Expression> where) where T : class, IResource; @@ -130,7 +121,6 @@ namespace Kyoo.Abstractions.Controllers /// The season's number /// If the item is not found /// The season found - [ItemNotNull] Task Get(int showID, int seasonNumber); /// @@ -140,7 +130,6 @@ namespace Kyoo.Abstractions.Controllers /// The season's number /// If the item is not found /// The season found - [ItemNotNull] Task Get(string showSlug, int seasonNumber); /// @@ -151,7 +140,6 @@ namespace Kyoo.Abstractions.Controllers /// The episode's number /// If the item is not found /// The episode found - [ItemNotNull] Task Get(int showID, int seasonNumber, int episodeNumber); /// @@ -162,7 +150,6 @@ namespace Kyoo.Abstractions.Controllers /// The episode's number /// If the item is not found /// The episode found - [ItemNotNull] Task Get(string showSlug, int seasonNumber, int episodeNumber); /// @@ -171,8 +158,7 @@ namespace Kyoo.Abstractions.Controllers /// The id of the resource /// The type of the resource /// The resource found - [ItemCanBeNull] - Task GetOrDefault(int id) + Task GetOrDefault(int id) where T : class, IResource; /// @@ -181,8 +167,7 @@ namespace Kyoo.Abstractions.Controllers /// The slug of the resource /// The type of the resource /// The resource found - [ItemCanBeNull] - Task GetOrDefault(string slug) + Task GetOrDefault(string slug) where T : class, IResource; /// @@ -192,8 +177,7 @@ namespace Kyoo.Abstractions.Controllers /// A custom sort method to handle cases where multiples items match the filters. /// The type of the resource /// The first resource found that match the where function - [ItemCanBeNull] - Task GetOrDefault(Expression> where, Sort sortBy = default) + Task GetOrDefault(Expression> where, Sort? sortBy = default) where T : class, IResource; /// @@ -202,8 +186,7 @@ namespace Kyoo.Abstractions.Controllers /// The id of the show /// The season's number /// The season found - [ItemCanBeNull] - Task GetOrDefault(int showID, int seasonNumber); + Task GetOrDefault(int showID, int seasonNumber); /// /// Get a season from it's show slug and it's seasonNumber or null if it is not found. @@ -211,8 +194,7 @@ namespace Kyoo.Abstractions.Controllers /// The slug of the show /// The season's number /// The season found - [ItemCanBeNull] - Task GetOrDefault(string showSlug, int seasonNumber); + Task GetOrDefault(string showSlug, int seasonNumber); /// /// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found. @@ -221,8 +203,7 @@ namespace Kyoo.Abstractions.Controllers /// The season's number /// The episode's number /// The episode found - [ItemCanBeNull] - Task GetOrDefault(int showID, int seasonNumber, int episodeNumber); + Task GetOrDefault(int showID, int seasonNumber, int episodeNumber); /// /// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found. @@ -231,8 +212,7 @@ namespace Kyoo.Abstractions.Controllers /// The season's number /// The episode's number /// The episode found - [ItemCanBeNull] - Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber); + Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber); /// /// Load a related resource @@ -248,7 +228,7 @@ namespace Kyoo.Abstractions.Controllers /// /// /// - Task Load([NotNull] T obj, Expression> member, bool force = false) + Task Load(T obj, Expression> member, bool force = false) where T : class, IResource where T2 : class, IResource; @@ -266,7 +246,7 @@ namespace Kyoo.Abstractions.Controllers /// /// /// - Task Load([NotNull] T obj, Expression>> member, bool force = false) + Task Load(T obj, Expression>> member, bool force = false) where T : class, IResource where T2 : class; @@ -283,7 +263,7 @@ namespace Kyoo.Abstractions.Controllers /// /// /// - Task Load([NotNull] T obj, string memberName, bool force = false) + Task Load(T obj, string memberName, bool force = false) where T : class, IResource; /// @@ -298,35 +278,7 @@ namespace Kyoo.Abstractions.Controllers /// /// /// A representing the asynchronous operation. - Task Load([NotNull] IResource obj, string memberName, bool force = false); - - /// - /// Get items (A wrapper around shows or collections) from a library. - /// - /// The ID of the library - /// A filter function - /// Sort information (sort order and sort by) - /// How many items to return and where to start - /// No library exist with the given ID. - /// A list of items that match every filters - Task> GetItemsFromLibrary(int id, - Expression> where = null, - Sort sort = default, - Pagination limit = default); - - /// - /// Get items (A wrapper around shows or collections) from a library. - /// - /// The slug of the library - /// A filter function - /// Sort information (sort order and sort by) - /// How many items to return and where to start - /// No library exist with the given slug. - /// A list of items that match every filters - Task> GetItemsFromLibrary(string slug, - Expression> where = null, - Sort sort = default, - Pagination limit = default); + Task Load(IResource obj, string memberName, bool force = false); /// /// Get people's roles from a show. @@ -338,9 +290,9 @@ namespace Kyoo.Abstractions.Controllers /// No exist with the given ID. /// A list of items that match every filters Task> GetPeopleFromShow(int showID, - Expression> where = null, - Sort sort = default, - Pagination limit = default); + Expression>? where = null, + Sort? sort = default, + Pagination? limit = default); /// /// Get people's roles from a show. @@ -352,9 +304,9 @@ namespace Kyoo.Abstractions.Controllers /// No exist with the given slug. /// A list of items that match every filters Task> GetPeopleFromShow(string showSlug, - Expression> where = null, - Sort sort = default, - Pagination limit = default); + Expression>? where = null, + Sort? sort = default, + Pagination? limit = default); /// /// Get people's roles from a person. @@ -366,9 +318,9 @@ namespace Kyoo.Abstractions.Controllers /// No exist with the given ID. /// A list of items that match every filters Task> GetRolesFromPeople(int id, - Expression> where = null, - Sort sort = default, - Pagination limit = default); + Expression>? where = null, + Sort? sort = default, + Pagination? limit = default); /// /// Get people's roles from a person. @@ -380,27 +332,9 @@ namespace Kyoo.Abstractions.Controllers /// No exist with the given slug. /// A list of items that match every filters Task> GetRolesFromPeople(string slug, - Expression> where = null, - Sort sort = default, - Pagination limit = default); - - /// - /// Setup relations between a show, a library and a collection - /// - /// The show's ID to setup relations with - /// The library's ID to setup relations with (optional) - /// The collection's ID to setup relations with (optional) - /// A representing the asynchronous operation. - Task AddShowLink(int showID, int? libraryID, int? collectionID); - - /// - /// Setup relations between a show, a library and a collection - /// - /// The show to setup relations with - /// The library to setup relations with (optional) - /// The collection to setup relations with (optional) - /// A representing the asynchronous operation. - Task AddShowLink([NotNull] Show show, Library library, Collection collection); + Expression>? where = null, + Sort? sort = default, + Pagination? limit = default); /// /// Get all resources with filters @@ -410,9 +344,9 @@ namespace Kyoo.Abstractions.Controllers /// How many items to return and where to start /// The type of resources to load /// A list of resources that match every filters - Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) + Task> GetAll(Expression>? where = null, + Sort? sort = default, + Pagination? limit = default) where T : class, IResource; /// @@ -421,7 +355,7 @@ namespace Kyoo.Abstractions.Controllers /// A filter function /// The type of resources to load /// A list of resources that match every filters - Task GetCount(Expression> where = null) + Task GetCount(Expression>? where = null) where T : class, IResource; /// @@ -439,7 +373,7 @@ namespace Kyoo.Abstractions.Controllers /// The item to register /// The type of resource /// The resource registers and completed by database's information (related items and so on) - Task Create([NotNull] T item) + Task Create(T item) where T : class, IResource; /// @@ -448,7 +382,7 @@ namespace Kyoo.Abstractions.Controllers /// The item to register /// The type of resource /// The newly created item or the existing value if it existed. - Task CreateIfNotExists([NotNull] T item) + Task CreateIfNotExists(T item) where T : class, IResource; /// diff --git a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs index 5c8e438d..ead00dbf 100644 --- a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs +++ b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; -using JetBrains.Annotations; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Exceptions; @@ -45,7 +44,6 @@ namespace Kyoo.Abstractions.Controllers /// The id of the resource /// If the item could not be found. /// The resource found - [ItemNotNull] Task Get(int id); /// @@ -54,7 +52,6 @@ namespace Kyoo.Abstractions.Controllers /// The slug of the resource /// If the item could not be found. /// The resource found - [ItemNotNull] Task Get(string slug); /// @@ -63,7 +60,6 @@ namespace Kyoo.Abstractions.Controllers /// A predicate to filter the resource. /// If the item could not be found. /// The resource found - [ItemNotNull] Task Get(Expression> where); /// @@ -71,16 +67,14 @@ namespace Kyoo.Abstractions.Controllers /// /// The id of the resource /// The resource found - [ItemCanBeNull] - Task GetOrDefault(int id); + Task GetOrDefault(int id); /// /// Get a resource from it's slug or null if it is not found. /// /// The slug of the resource /// The resource found - [ItemCanBeNull] - Task GetOrDefault(string slug); + Task GetOrDefault(string slug); /// /// Get the first resource that match the predicate or null if it is not found. @@ -88,15 +82,13 @@ namespace Kyoo.Abstractions.Controllers /// A predicate to filter the resource. /// A custom sort method to handle cases where multiples items match the filters. /// The resource found - [ItemCanBeNull] - Task GetOrDefault(Expression> where, Sort sortBy = default); + Task GetOrDefault(Expression> where, Sort? sortBy = default); /// /// Search for resources. /// /// The query string. /// A list of resources found - [ItemNotNull] Task> Search(string query); /// @@ -106,33 +98,30 @@ namespace Kyoo.Abstractions.Controllers /// Sort information about the query (sort by, sort order) /// How pagination should be done (where to start and how many to return) /// A list of resources that match every filters - [ItemNotNull] - Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default); + Task> GetAll(Expression>? where = null, + Sort? sort = default, + Pagination? limit = default); /// /// Get the number of resources that match the filter's predicate. /// /// A filter predicate /// How many resources matched that filter - Task GetCount(Expression> where = null); + Task GetCount(Expression>? where = null); /// /// Create a new resource. /// /// The item to register /// The resource registers and completed by database's information (related items and so on) - [ItemNotNull] - Task Create([NotNull] T obj); + Task Create(T obj); /// /// Create a new resource if it does not exist already. If it does, the existing value is returned instead. /// /// The object to create /// The newly created item or the existing value if it existed. - [ItemNotNull] - Task CreateIfNotExists([NotNull] T obj); + Task CreateIfNotExists(T obj); /// /// Called when a resource has been created. @@ -146,8 +135,7 @@ namespace Kyoo.Abstractions.Controllers /// Should old properties of the resource be discarded or should null values considered as not changed? /// If the item is not found /// The resource edited and completed by database's information (related items and so on) - [ItemNotNull] - Task Edit([NotNull] T edited, bool resetOld); + Task Edit(T edited, bool resetOld); /// /// Called when a resource has been edited. @@ -176,14 +164,14 @@ namespace Kyoo.Abstractions.Controllers /// The resource to delete /// If the item is not found /// A representing the asynchronous operation. - Task Delete([NotNull] T obj); + Task Delete(T obj); /// /// Delete all resources that match the predicate. /// /// A predicate to filter resources to delete. Every resource that match this will be deleted. /// A representing the asynchronous operation. - Task DeleteAll([NotNull] Expression> where); + Task DeleteAll(Expression> where); /// /// Called when a resource has been edited. @@ -202,21 +190,16 @@ namespace Kyoo.Abstractions.Controllers Type RepositoryType { get; } } + /// + /// A repository to handle shows. + /// + public interface IMovieRepository : IRepository { } + /// /// A repository to handle shows. /// public interface IShowRepository : IRepository { - /// - /// Link a show to a collection and/or a library. The given show is now part of those containers. - /// If both a library and a collection are given, the collection is added to the library too. - /// - /// The ID of the show - /// The ID of the library (optional) - /// The ID of the collection (optional) - /// A representing the asynchronous operation. - Task AddShowLink(int showID, int? libraryID, int? collectionID); - /// /// Get a show's slug from it's ID. /// @@ -330,55 +313,16 @@ namespace Kyoo.Abstractions.Controllers Task GetAbsolute(string showSlug, int absoluteNumber); } - /// - /// A repository to handle libraries. - /// - public interface ILibraryRepository : IRepository { } - /// /// A repository to handle library items (A wrapper around shows and collections). /// - public interface ILibraryItemRepository : IRepository - { - /// - /// Get items (A wrapper around shows or collections) from a library. - /// - /// The ID of the library - /// A filter function - /// Sort information (sort order and sort by) - /// How many items to return and where to start - /// No library exist with the given ID. - /// A list of items that match every filters - public Task> GetFromLibrary(int id, - Expression> where = null, - Sort sort = default, - Pagination limit = default); - - /// - /// Get items (A wrapper around shows or collections) from a library. - /// - /// The slug of the library - /// A filter function - /// Sort information (sort order and sort by) - /// How many items to return and where to start - /// No library exist with the given slug. - /// A list of items that match every filters - public Task> GetFromLibrary(string slug, - Expression> where = null, - Sort sort = default, - Pagination limit = default); - } + public interface ILibraryItemRepository : IRepository { } /// /// A repository for collections /// public interface ICollectionRepository : IRepository { } - /// - /// A repository for genres. - /// - public interface IGenreRepository : IRepository { } - /// /// A repository for studios. /// @@ -399,9 +343,9 @@ namespace Kyoo.Abstractions.Controllers /// No exist with the given ID. /// A list of items that match every filters Task> GetFromShow(int showID, - Expression> where = null, - Sort sort = default, - Pagination limit = default); + Expression>? where = null, + Sort? sort = default, + Pagination? limit = default); /// /// Get people's roles from a show. @@ -413,9 +357,9 @@ namespace Kyoo.Abstractions.Controllers /// No exist with the given slug. /// A list of items that match every filters Task> GetFromShow(string showSlug, - Expression> where = null, - Sort sort = default, - Pagination limit = default); + Expression>? where = null, + Sort? sort = default, + Pagination? limit = default); /// /// Get people's roles from a person. @@ -427,9 +371,9 @@ namespace Kyoo.Abstractions.Controllers /// No exist with the given ID. /// A list of items that match every filters Task> GetFromPeople(int id, - Expression> where = null, - Sort sort = default, - Pagination limit = default); + Expression>? where = null, + Sort? sort = default, + Pagination? limit = default); /// /// Get people's roles from a person. @@ -441,9 +385,9 @@ namespace Kyoo.Abstractions.Controllers /// No exist with the given slug. /// A list of items that match every filters Task> GetFromPeople(string slug, - Expression> where = null, - Sort sort = default, - Pagination limit = default); + Expression>? where = null, + Sort? sort = default, + Pagination? limit = default); } /// diff --git a/back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj b/back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj index 9d8ddb0c..3433f9f1 100644 --- a/back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj +++ b/back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj @@ -3,6 +3,7 @@ Kyoo.Abstractions Base package to create plugins for Kyoo. Kyoo.Abstractions + enable diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs index cd946714..228dc187 100644 --- a/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs +++ b/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs @@ -32,7 +32,7 @@ namespace Kyoo.Abstractions.Models.Attributes /// /// The public name of this api. /// - [NotNull] public string Name { get; } + public string Name { get; } /// /// The name of the group in witch this API is. You can also specify a custom sort order using the following @@ -45,7 +45,7 @@ namespace Kyoo.Abstractions.Models.Attributes /// Create a new . /// /// The name of the api that will be used on the documentation page. - public ApiDefinitionAttribute([NotNull] string name) + public ApiDefinitionAttribute(string name) { if (name == null) throw new ArgumentNullException(nameof(name)); diff --git a/back/src/Kyoo.Abstractions/Models/ConfigurationReference.cs b/back/src/Kyoo.Abstractions/Models/ConfigurationReference.cs index 635bbb97..76294ca2 100644 --- a/back/src/Kyoo.Abstractions/Models/ConfigurationReference.cs +++ b/back/src/Kyoo.Abstractions/Models/ConfigurationReference.cs @@ -60,7 +60,7 @@ namespace Kyoo.Abstractions.Models /// /// The type of the object /// The list of configuration reference a type has. - public static IEnumerable CreateReference(string path, [NotNull] Type type) + public static IEnumerable CreateReference(string path, Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); diff --git a/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs b/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs index 0cd21975..c608b63a 100644 --- a/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs +++ b/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs @@ -36,7 +36,7 @@ namespace Kyoo.Abstractions.Models.Exceptions /// Create a new with the default message. /// /// The existing object. - public DuplicatedItemException(object existing = null) + public DuplicatedItemException(object? existing = null) : base("Already exists in the database.") { Existing = existing; diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/ILink.cs b/back/src/Kyoo.Abstractions/Models/Genre.cs similarity index 75% rename from back/src/Kyoo.Abstractions/Models/Resources/Interfaces/ILink.cs rename to back/src/Kyoo.Abstractions/Models/Genre.cs index 5c146786..2b7e16f0 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/ILink.cs +++ b/back/src/Kyoo.Abstractions/Models/Genre.cs @@ -19,13 +19,27 @@ namespace Kyoo.Abstractions.Models { /// - /// An interface to represent resources that should have a link field in their return values (like videos). + /// A genre that allow one to specify categories for shows. /// - public interface ILink + public enum Genre { - /// - /// The link to return, in most cases this should be a string. - /// - public object Link { get; } + Action, + Adventure, + Animation, + Comedy, + Crime, + Documentary, + Drama, + Family, + Fantasy, + History, + Horror, + Music, + Mystery, + Romance, + ScienceFiction, + Thriller, + War, + Western, } } diff --git a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs index 852e48f9..0b66d2e1 100644 --- a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs +++ b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs @@ -17,16 +17,13 @@ // along with Kyoo. If not, see . using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq.Expressions; namespace Kyoo.Abstractions.Models { /// /// The type of item, ether a show, a movie or a collection. /// - public enum ItemType + public enum ItemKind { /// /// The is a . @@ -49,138 +46,67 @@ namespace Kyoo.Abstractions.Models /// A type union between and . /// This is used to list content put inside a library. /// - public class LibraryItem : CustomTypeDescriptor, IResource, IThumbnails + public interface ILibraryItem : IResource { - /// + /// + /// Is the item a collection, a movie or a show? + /// + public ItemKind Kind { get; } + + public string Name { get; } + + public DateTime? AirDate { get; } + + public Image Poster { get; } + } + + public class BagItem : ILibraryItem + { + public ItemKind Kind { get; } + public int ID { get; set; } - /// - public string Slug { get; set; } + public string Slug { get; set; } - /// - /// The title of the show or collection. - /// - public string Title { get; set; } + public string Name { get; set; } - /// - /// The summary of the show or collection. - /// - public string Overview { get; set; } + public DateTime? AirDate { get; set; } - /// - /// Is this show airing, not aired yet or finished? This is only applicable for shows. - /// - public Status? Status { get; set; } - - /// - /// The date this show or collection started airing. It can be null if this is unknown. - /// - public DateTime? StartAir { get; set; } - - /// - /// The date this show or collection finished airing. - /// It must be after the but can be the same (example: for movies). - /// It can also be null if this is unknown. - /// - public DateTime? EndAir { get; set; } - - /// public Image Poster { get; set; } - /// - public Image Thumbnail { get; set; } + public object Rest { get; set; } - /// - public Image Logo { get; set; } - - /// - /// The type of this item (ether a collection, a show or a movie). - /// - public ItemType Type { get; set; } - - /// - /// Create a new, empty . - /// - public LibraryItem() { } - - /// - /// Create a from a show. - /// - /// The show that this library item should represent. - public LibraryItem(Show show) + public ILibraryItem ToItem() { - ID = show.ID; - Slug = show.Slug; - Title = show.Title; - Overview = show.Overview; - Status = show.Status; - StartAir = show.StartAir; - EndAir = show.EndAir; - Poster = show.Poster; - Thumbnail = show.Thumbnail; - Logo = show.Logo; - Type = show.IsMovie ? ItemType.Movie : ItemType.Show; - } - - /// - /// Create a from a collection - /// - /// The collection that this library item should represent. - public LibraryItem(Collection collection) - { - ID = -collection.ID; - Slug = collection.Slug; - Title = collection.Name; - Overview = collection.Overview; - Status = Models.Status.Unknown; - StartAir = null; - EndAir = null; - Poster = collection.Poster; - Thumbnail = collection.Thumbnail; - Logo = collection.Logo; - Type = ItemType.Collection; - } - - /// - /// An expression to create a representing a show. - /// - public static Expression> FromShow => x => new LibraryItem - { - ID = x.ID, - Slug = x.Slug, - Title = x.Title, - Overview = x.Overview, - Status = x.Status, - StartAir = x.StartAir, - EndAir = x.EndAir, - Poster = x.Poster, - Thumbnail = x.Thumbnail, - Logo = x.Logo, - Type = x.IsMovie ? ItemType.Movie : ItemType.Show - }; - - /// - /// An expression to create a representing a collection. - /// - public static Expression> FromCollection => x => new LibraryItem - { - ID = -x.ID, - Slug = x.Slug, - Title = x.Name, - Overview = x.Overview, - Status = Models.Status.Unknown, - StartAir = null, - EndAir = null, - Poster = x.Poster, - Thumbnail = x.Thumbnail, - Logo = x.Logo, - Type = ItemType.Collection - }; - - /// - public override string GetClassName() - { - return Type.ToString(); + return Kind switch + { + ItemKind.Movie => Rest as MovieItem, + ItemKind.Show => Rest as ShowItem, + ItemKind.Collection => Rest as CollectionItem, + }; } } + + public sealed class ShowItem : Show, ILibraryItem + { + /// + public ItemKind Kind => ItemKind.Show; + + public DateTime? AirDate => StartAir; + } + + public sealed class MovieItem : Movie, ILibraryItem + { + /// + public ItemKind Kind => ItemKind.Movie; + } + + public sealed class CollectionItem : Collection, ILibraryItem + { + /// + public ItemKind Kind => ItemKind.Collection; + + public DateTime? AirDate => null; + } + } diff --git a/back/src/Kyoo.Abstractions/Models/PeopleRole.cs b/back/src/Kyoo.Abstractions/Models/PeopleRole.cs index 3a416927..dab757a1 100644 --- a/back/src/Kyoo.Abstractions/Models/PeopleRole.cs +++ b/back/src/Kyoo.Abstractions/Models/PeopleRole.cs @@ -53,12 +53,16 @@ namespace Kyoo.Abstractions.Models /// /// The ID of the Show where the People playing in. /// - public int ShowID { get; set; } + public int? ShowID { get; set; } /// /// The show where the People played in. /// - public Show Show { get; set; } + public Show? Show { get; set; } + + public int? MovieID { get; set; } + + public Movie? Movie { get; set; } /// /// The type of work the person has done for the show. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs index e3fa14cf..a6f3043f 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs @@ -17,7 +17,10 @@ // along with Kyoo. If not, see . using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Utils; +using Newtonsoft.Json; namespace Kyoo.Abstractions.Models { @@ -31,7 +34,7 @@ namespace Kyoo.Abstractions.Models public int ID { get; set; } /// - public string Slug { get; set; } + [MaxLength(256)] public string Slug { get; set; } /// /// The name of this collection. @@ -39,30 +42,39 @@ namespace Kyoo.Abstractions.Models public string Name { get; set; } /// - public Image Poster { get; set; } + public Image? Poster { get; set; } /// - public Image Thumbnail { get; set; } + public Image? Thumbnail { get; set; } /// - public Image Logo { get; set; } + public Image? Logo { get; set; } /// /// The description of this collection. /// - public string Overview { get; set; } + public string? Overview { get; set; } + + /// + /// The list of movies contained in this collection. + /// + [LoadableRelation] public ICollection? Movies { get; set; } /// /// The list of shows contained in this collection. /// - [LoadableRelation] public ICollection Shows { get; set; } - - /// - /// The list of libraries that contains this collection. - /// - [LoadableRelation] public ICollection Libraries { get; set; } + [LoadableRelation] public ICollection? Shows { get; set; } /// - public Dictionary ExternalId { get; set; } + public Dictionary ExternalId { get; set; } = new(); + + public Collection() { } + + [JsonConstructor] + public Collection(string name) + { + Slug = Utility.ToSlug(name); + Name = name; + } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index 86e11442..87b20a5e 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; using JetBrains.Annotations; using Kyoo.Abstractions.Controllers; @@ -35,24 +36,19 @@ namespace Kyoo.Abstractions.Models /// [Computed] + [MaxLength(256)] public string Slug { get { if (ShowSlug != null || Show?.Slug != null) return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); - return ShowID != 0 - ? GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber) - : null; + return GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber); } [UsedImplicitly] - [NotNull] private set { - if (value == null) - throw new ArgumentNullException(nameof(value)); - Match match = Regex.Match(value, @"(?.+)-s(?\d+)e(?\d+)"); if (match.Success) @@ -80,7 +76,7 @@ namespace Kyoo.Abstractions.Models /// /// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed. /// - [SerializeIgnore] public string ShowSlug { private get; set; } + [SerializeIgnore] public string? ShowSlug { private get; set; } /// /// The ID of the Show containing this episode. @@ -90,7 +86,7 @@ namespace Kyoo.Abstractions.Models /// /// The show that contains this episode. This must be explicitly loaded via a call to . /// - [LoadableRelation(nameof(ShowID))] public Show Show { get; set; } + [LoadableRelation(nameof(ShowID))] public Show? Show { get; set; } /// /// The ID of the Season containing this episode. @@ -105,7 +101,7 @@ namespace Kyoo.Abstractions.Models /// This can be null if the season is unknown and the episode is only identified /// by it's . /// - [LoadableRelation(nameof(SeasonID))] public Season Season { get; set; } + [LoadableRelation(nameof(SeasonID))] public Season? Season { get; set; } /// /// The season in witch this episode is in. @@ -130,12 +126,12 @@ namespace Kyoo.Abstractions.Models /// /// The title of this episode. /// - public string Title { get; set; } + public string? Name { get; set; } /// /// The overview of this episode. /// - public string Overview { get; set; } + public string? Overview { get; set; } /// /// The release date of this episode. It can be null if unknown. @@ -143,16 +139,16 @@ namespace Kyoo.Abstractions.Models public DateTime? ReleaseDate { get; set; } /// - public Image Poster { get; set; } + public Image? Poster { get; set; } /// - public Image Thumbnail { get; set; } + public Image? Thumbnail { get; set; } /// - public Image Logo { get; set; } + public Image? Logo { get; set; } /// - public Dictionary ExternalId { get; set; } + public Dictionary ExternalId { get; set; } = new(); /// /// Get the slug of an episode. @@ -172,7 +168,7 @@ namespace Kyoo.Abstractions.Models /// /// The slug corresponding to the given arguments /// The given show slug was null. - public static string GetSlug([NotNull] string showSlug, + public static string GetSlug(string showSlug, int? seasonNumber, int? episodeNumber, int? absoluteNumber = null) diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Genre.cs b/back/src/Kyoo.Abstractions/Models/Resources/Genre.cs deleted file mode 100644 index 5bf8ceee..00000000 --- a/back/src/Kyoo.Abstractions/Models/Resources/Genre.cs +++ /dev/null @@ -1,62 +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 genre that allow one to specify categories for shows. - /// - public class Genre : IResource - { - /// - public int ID { get; set; } - - /// - public string Slug { get; set; } - - /// - /// The name of this genre. - /// - public string Name { get; set; } - - /// - /// The list of shows that have this genre. - /// - [LoadableRelation] public ICollection Shows { get; set; } - - /// - /// Create a new, empty . - /// - public Genre() { } - - /// - /// Create a new and specify it's . - /// The is automatically calculated from it's name. - /// - /// The name of this genre. - public Genre(string name) - { - Slug = Utility.ToSlug(name); - Name = name; - } - } -} diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs index 509006e5..69e77f21 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . +using System.ComponentModel.DataAnnotations; using Kyoo.Abstractions.Controllers; namespace Kyoo.Abstractions.Models @@ -42,6 +43,7 @@ namespace Kyoo.Abstractions.Models /// There is no setter for a slug since it can be computed from other fields. /// For example, a season slug is {ShowSlug}-s{SeasonNumber}. /// + [MaxLength(256)] public string Slug { get; } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs index c69c984d..10e1b742 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs @@ -28,18 +28,18 @@ namespace Kyoo.Abstractions.Models /// /// A poster is a 9/16 format image with the cover of the resource. /// - public Image Poster { get; set; } + 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 Image Thumbnail { get; set; } + public Image? Thumbnail { get; set; } /// /// A logo is a small image representing the resource. /// - public Image Logo { get; set; } + public Image? Logo { get; set; } } public class Image diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs new file mode 100644 index 00000000..a6f8ec27 --- /dev/null +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -0,0 +1,140 @@ +// 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.ComponentModel.DataAnnotations; +using Kyoo.Abstractions.Controllers; +using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Utils; +using Newtonsoft.Json; + +namespace Kyoo.Abstractions.Models +{ + /// + /// A series or a movie. + /// + public class Movie : IResource, IMetadata, IOnMerge, IThumbnails + { + /// + public int ID { get; set; } + + /// + [MaxLength(256)] + public string Slug { get; set; } + + /// + /// The title of this show. + /// + public string Name { get; set; } + + /// + /// A catchphrase for this movie. + /// + public string? Tagline { get; set; } + + /// + /// The list of alternative titles of this show. + /// + public string[] Aliases { get; set; } = Array.Empty(); + + /// + /// The path of the movie video file. + /// + public string Path { get; set; } + + /// + /// The summary of this show. + /// + public string? Overview { get; set; } + + /// + /// A list of tags that match this movie. + /// + public string[] Tags { get; set; } = Array.Empty(); + + /// + /// The list of genres (themes) this show has. + /// + public Genre[] Genres { get; set; } = Array.Empty(); + + /// + /// Is this show airing, not aired yet or finished? + /// + public Status Status { get; set; } + + /// + /// The date this movie aired. + /// + public DateTime? AirDate { 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; } = new(); + + /// + /// The ID of the Studio that made this show. + /// + [SerializeIgnore] public int? StudioID { get; set; } + + /// + /// The Studio that made this show. + /// This must be explicitly loaded via a call to . + /// + [LoadableRelation(nameof(StudioID))][EditableRelation] public Studio? Studio { get; set; } + + /// + /// The list of people that made this show. + /// + [LoadableRelation][EditableRelation] public ICollection? People { get; set; } + + /// + /// The list of collections that contains this show. + /// + [LoadableRelation] public ICollection? Collections { get; set; } + + /// + public void OnMerge(object merged) + { + foreach (PeopleRole link in People) + link.Movie = this; + } + + public Movie() { } + + [JsonConstructor] + public Movie(string name) + { + Slug = Utility.ToSlug(name); + Name = name; + } + } +} diff --git a/back/src/Kyoo.Abstractions/Models/Resources/People.cs b/back/src/Kyoo.Abstractions/Models/Resources/People.cs index c5a32429..ed56d5e5 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/People.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/People.cs @@ -17,7 +17,10 @@ // along with Kyoo. If not, see . using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Utils; +using Newtonsoft.Json; namespace Kyoo.Abstractions.Models { @@ -30,6 +33,7 @@ namespace Kyoo.Abstractions.Models public int ID { get; set; } /// + [MaxLength(256)] public string Slug { get; set; } /// @@ -38,20 +42,29 @@ namespace Kyoo.Abstractions.Models public string Name { get; set; } /// - public Image Poster { get; set; } + public Image? Poster { get; set; } /// - public Image Thumbnail { get; set; } + public Image? Thumbnail { get; set; } /// - public Image Logo { get; set; } + public Image? Logo { get; set; } /// - public Dictionary ExternalId { get; set; } + public Dictionary ExternalId { get; set; } = new(); /// /// The list of roles this person has played in. See for more information. /// - [EditableRelation][LoadableRelation] public ICollection Roles { get; set; } + [EditableRelation][LoadableRelation] public ICollection? Roles { get; set; } + + public People() { } + + [JsonConstructor] + public People(string name) + { + Slug = Utility.ToSlug(name); + Name = name; + } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs index 1b31d2c3..0d0e0130 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; using JetBrains.Annotations; using Kyoo.Abstractions.Controllers; @@ -35,6 +36,7 @@ namespace Kyoo.Abstractions.Models /// [Computed] + [MaxLength(256)] public string Slug { get @@ -48,7 +50,7 @@ namespace Kyoo.Abstractions.Models [NotNull] private set { - Match match = Regex.Match(value ?? string.Empty, @"(?.+)-s(?\d+)"); + Match match = Regex.Match(value, @"(?.+)-s(?\d+)"); if (!match.Success) throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); @@ -60,7 +62,7 @@ namespace Kyoo.Abstractions.Models /// /// The slug of the Show that contain this episode. If this is not set, this season is ill-formed. /// - [SerializeIgnore] public string ShowSlug { private get; set; } + [SerializeIgnore] public string? ShowSlug { private get; set; } /// /// The ID of the Show containing this season. @@ -71,7 +73,7 @@ namespace Kyoo.Abstractions.Models /// The show that contains this season. /// This must be explicitly loaded via a call to . /// - [LoadableRelation(nameof(ShowID))] public Show Show { get; set; } + [LoadableRelation(nameof(ShowID))] public Show? Show { get; set; } /// /// The number of this season. This can be set to 0 to indicate specials. @@ -81,12 +83,12 @@ namespace Kyoo.Abstractions.Models /// /// The title of this season. /// - public string Title { get; set; } + public string? Name { get; set; } /// /// A quick overview of this season. /// - public string Overview { get; set; } + public string? Overview { get; set; } /// /// The starting air date of this season. @@ -99,20 +101,20 @@ namespace Kyoo.Abstractions.Models public DateTime? EndDate { get; set; } /// - public Image Poster { get; set; } + public Image? Poster { get; set; } /// - public Image Thumbnail { get; set; } + public Image? Thumbnail { get; set; } /// - public Image Logo { get; set; } + public Image? Logo { get; set; } /// - public Dictionary ExternalId { get; set; } + public Dictionary ExternalId { get; set; } = new(); /// /// The list of episodes that this season contains. /// - [LoadableRelation] public ICollection Episodes { get; set; } + [LoadableRelation] public ICollection? Episodes { get; set; } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs index 0dc30b30..30da3693 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -18,8 +18,11 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Utils; +using Newtonsoft.Json; namespace Kyoo.Abstractions.Models { @@ -32,27 +35,38 @@ namespace Kyoo.Abstractions.Models public int ID { get; set; } /// + [MaxLength(256)] public string Slug { get; set; } /// /// The title of this show. /// - public string Title { get; set; } + public string Name { get; set; } + + /// + /// A catchphrase for this show. + /// + public string? Tagline { get; set; } /// /// The list of alternative titles of this show. /// - [EditableRelation] public string[] Aliases { get; set; } - - /// - /// The path of the root directory of this show. - /// - [SerializeIgnore] public string Path { get; set; } + public string[] Aliases { get; set; } = Array.Empty(); /// /// The summary of this show. /// - public string Overview { get; set; } + public string? Overview { get; set; } + + /// + /// A list of tags that match this movie. + /// + public string[] Tags { get; set; } = Array.Empty(); + + /// + /// The list of genres (themes) this show has. + /// + public Genre[] Genres { get; set; } = Array.Empty(); /// /// Is this show airing, not aired yet or finished? @@ -71,27 +85,22 @@ namespace Kyoo.Abstractions.Models /// public DateTime? EndAir { get; set; } - /// - /// True if this show represent a movie, false otherwise. - /// - public bool IsMovie { get; set; } + /// + public Image? Poster { get; set; } /// - public Image Poster { get; set; } + public Image? Thumbnail { get; set; } /// - public Image Thumbnail { get; set; } - - /// - public Image Logo { get; set; } + public Image? Logo { get; set; } /// /// A video of a few minutes that tease the content. /// - public string Trailer { get; set; } + public string? Trailer { get; set; } /// - public Dictionary ExternalId { get; set; } + public Dictionary ExternalId { get; set; } = new(); /// /// The ID of the Studio that made this show. @@ -102,39 +111,29 @@ namespace Kyoo.Abstractions.Models /// The Studio that made this show. /// This must be explicitly loaded via a call to . /// - [LoadableRelation(nameof(StudioID))][EditableRelation] public Studio Studio { get; set; } - - /// - /// The list of genres (themes) this show has. - /// - [LoadableRelation][EditableRelation] public ICollection Genres { get; set; } + [LoadableRelation(nameof(StudioID))][EditableRelation] public Studio? Studio { get; set; } /// /// The list of people that made this show. /// - [LoadableRelation][EditableRelation] public ICollection People { get; set; } + [LoadableRelation][EditableRelation] public ICollection? People { get; set; } /// /// The different seasons in this show. If this is a movie, this list is always null or empty. /// - [LoadableRelation] public ICollection Seasons { get; set; } + [LoadableRelation] public ICollection? Seasons { get; set; } /// /// The list of episodes in this show. /// If this is a movie, there will be a unique episode (with the seasonNumber and episodeNumber set to null). /// Having an episode is necessary to store metadata and tracks. /// - [LoadableRelation] public ICollection Episodes { get; set; } - - /// - /// The list of libraries that contains this show. - /// - [LoadableRelation] public ICollection Libraries { get; set; } + [LoadableRelation] public ICollection? Episodes { get; set; } /// /// The list of collections that contains this show. /// - [LoadableRelation] public ICollection Collections { get; set; } + [LoadableRelation] public ICollection? Collections { get; set; } /// public void OnMerge(object merged) @@ -157,6 +156,15 @@ namespace Kyoo.Abstractions.Models episode.Show = this; } } + + public Show() { } + + [JsonConstructor] + public Show(string name) + { + Slug = Utility.ToSlug(name); + Name = name; + } } /// diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs b/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs index e3923d3a..ab61b149 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs @@ -17,8 +17,10 @@ // along with Kyoo. If not, see . using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Kyoo.Abstractions.Models.Attributes; using Kyoo.Utils; +using Newtonsoft.Json; namespace Kyoo.Abstractions.Models { @@ -31,6 +33,7 @@ namespace Kyoo.Abstractions.Models public int ID { get; set; } /// + [MaxLength(256)] public string Slug { get; set; } /// @@ -41,10 +44,15 @@ namespace Kyoo.Abstractions.Models /// /// The list of shows that are made by this studio. /// - [LoadableRelation] public ICollection Shows { get; set; } + [LoadableRelation] public ICollection? Shows { get; set; } + + /// + /// The list of movies that are made by this studio. + /// + [LoadableRelation] public ICollection? Movies { get; set; } /// - public Dictionary ExternalId { get; set; } + public Dictionary ExternalId { get; set; } = new(); /// /// Create a new, empty, . @@ -55,6 +63,7 @@ namespace Kyoo.Abstractions.Models /// Create a new with a specific name, the slug is calculated automatically. /// /// The name of the studio. + [JsonConstructor] public Studio(string name) { Slug = Utility.ToSlug(name); diff --git a/back/src/Kyoo.Abstractions/Models/Resources/User.cs b/back/src/Kyoo.Abstractions/Models/Resources/User.cs index 2fde3468..3b5168a7 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/User.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/User.cs @@ -17,7 +17,10 @@ // along with Kyoo. If not, see . using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Utils; +using Newtonsoft.Json; namespace Kyoo.Abstractions.Models { @@ -30,6 +33,7 @@ namespace Kyoo.Abstractions.Models public int ID { get; set; } /// + [MaxLength(256)] public string Slug { get; set; } /// @@ -53,27 +57,30 @@ namespace Kyoo.Abstractions.Models /// public string[] Permissions { get; set; } - /// - /// Arbitrary extra data that can be used by specific authentication implementations. - /// - [SerializeIgnore] - public Dictionary ExtraData { get; set; } - /// /// A logo is a small image representing the resource. /// - public Image Logo { get; set; } + public Image? Logo { get; set; } /// /// The list of shows the user has finished. /// [SerializeIgnore] - public ICollection Watched { get; set; } + public ICollection? Watched { get; set; } /// /// The list of episodes the user is watching (stopped in progress or the next episode of the show) /// [SerializeIgnore] - public ICollection CurrentlyWatching { get; set; } + public ICollection? CurrentlyWatching { get; set; } + + public User() { } + + [JsonConstructor] + public User(string username) + { + Slug = Utility.ToSlug(username); + Username = username; + } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs b/back/src/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs index 66a11a3c..b5a76d68 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs @@ -36,7 +36,7 @@ namespace Kyoo.Abstractions.Models /// /// The started. /// - public Episode Episode { get; set; } + public Episode? Episode { get; set; } /// /// Where the player has stopped watching the episode (between 0 and 100). diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs index 0c56b2b1..97c8c8e9 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs @@ -58,7 +58,7 @@ namespace Kyoo.Abstractions.Models.Utils /// Create a new for the given slug. /// /// The slug of the resource. - public Identifier([NotNull] string slug) + public Identifier(string slug) { if (slug == null) throw new ArgumentNullException(nameof(slug)); diff --git a/back/src/Kyoo.Abstractions/Models/Utils/RequestError.cs b/back/src/Kyoo.Abstractions/Models/Utils/RequestError.cs index fa40fc64..2d55d4ed 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/RequestError.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/RequestError.cs @@ -31,13 +31,13 @@ namespace Kyoo.Abstractions.Models.Utils /// The list of errors that where made in the request. /// /// ["InvalidFilter: no field 'startYear' on a collection"] - [NotNull] public string[] Errors { get; set; } + public string[] Errors { get; set; } /// /// Create a new with one error. /// /// The error to specify in the response. - public RequestError([NotNull] string error) + public RequestError(string error) { if (error == null) throw new ArgumentNullException(nameof(error)); @@ -48,7 +48,7 @@ namespace Kyoo.Abstractions.Models.Utils /// Create a new with multiple errors. /// /// The errors to specify in the response. - public RequestError([NotNull] string[] errors) + public RequestError(string[] errors) { if (errors == null || !errors.Any()) throw new ArgumentException("Errors must be non null and not empty", nameof(errors)); diff --git a/back/src/Kyoo.Abstractions/Models/WatchItem.cs b/back/src/Kyoo.Abstractions/Models/WatchItem.cs deleted file mode 100644 index 7ec67e4b..00000000 --- a/back/src/Kyoo.Abstractions/Models/WatchItem.cs +++ /dev/null @@ -1,194 +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.ComponentModel; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models.Attributes; -using Newtonsoft.Json; - -namespace Kyoo.Abstractions.Models -{ - /// - /// A watch item give information useful for playback. - /// Information about tracks and display information that could be used by the player. - /// This contains mostly data from an with another form. - /// - public class WatchItem : CustomTypeDescriptor, IThumbnails, ILink - { - /// - /// The ID of the episode associated with this item. - /// - public int EpisodeID { get; set; } - - /// - /// The slug of this episode. - /// - public string Slug { get; set; } - - /// - /// The title of the show containing this episode. - /// - public string ShowTitle { get; set; } - - /// - /// The slug of the show containing this episode - /// - public string ShowSlug { get; set; } - - /// - /// The season in witch this episode is in. - /// - public int? SeasonNumber { get; set; } - - /// - /// The number of this episode is it's season. - /// - public int? EpisodeNumber { get; set; } - - /// - /// The absolute number of this episode. It's an episode number that is not reset to 1 after a new season. - /// - public int? AbsoluteNumber { get; set; } - - /// - /// The title of this episode. - /// - public string Title { get; set; } - - /// - /// The summary of this episode. - /// - public string Overview { get; set; } - - /// - /// The release date of this episode. It can be null if unknown. - /// - public DateTime? ReleaseDate { get; set; } - - /// - /// The episode that come before this one if you follow usual watch orders. - /// If this is the first episode or this is a movie, it will be null. - /// - public Episode PreviousEpisode { get; set; } - - /// - /// The episode that come after this one if you follow usual watch orders. - /// If this is the last aired episode or this is a movie, it will be null. - /// - public Episode NextEpisode { get; set; } - - /// - /// true if this is a movie, false otherwise. - /// - public bool IsMovie { 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... - /// - public object Info { get; set; } - - [SerializeIgnore] - private string _Type => IsMovie ? "movie" : "episode"; - - /// - public object Link => new - { - Direct = $"/video/{_Type}/{Slug}/direct", - Hls = $"/video/{_Type}/{Slug}/master.m3u8", - }; - - /// - /// Create a from an . - /// - /// The episode to transform. - /// - /// A library manager to retrieve the next and previous episode and load the show and tracks of the episode. - /// - /// A http client to reach the transcoder. - /// A new WatchItem representing the given episode. - public static async Task FromEpisode(Episode ep, ILibraryManager library, HttpClient client) - { - await library.Load(ep, x => x.Show); - - return new WatchItem - { - EpisodeID = ep.ID, - Slug = ep.Slug, - ShowSlug = ep.Show.Slug, - ShowTitle = ep.Show.Title, - SeasonNumber = ep.SeasonNumber, - EpisodeNumber = ep.EpisodeNumber, - AbsoluteNumber = ep.AbsoluteNumber, - Title = ep.Title, - Overview = ep.Overview, - ReleaseDate = ep.ReleaseDate, - Poster = ep.Poster, - Thumbnail = ep.Thumbnail, - Logo = ep.Logo, - PreviousEpisode = ep.Show.IsMovie - ? null - : (await library.GetAll( - where: x => x.ShowID == ep.ShowID, - limit: new Pagination(1, ep.ID, true) - )).FirstOrDefault(), - NextEpisode = ep.Show.IsMovie - ? null - : (await library.GetAll( - where: x => x.ShowID == ep.ShowID, - limit: new Pagination(1, ep.ID) - )).FirstOrDefault(), - IsMovie = ep.Show.IsMovie, - Info = await _GetInfo(ep, client), - }; - } - - private static async Task _GetInfo(Episode ep, HttpClient client) - { - HttpResponseMessage ret = await client.GetAsync($"http://transcoder:7666/{(ep.Show.IsMovie ? "movie" : "episode")}/{ep.Slug}/info"); - ret.EnsureSuccessStatusCode(); - string content = await ret.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(content); - } - - /// - public override string GetClassName() - { - return nameof(Show); - } - - /// - public override string GetComponentName() - { - return ShowSlug; - } - } -} diff --git a/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs b/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs index 34ba106a..fd1e4f4d 100644 --- a/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs +++ b/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs @@ -39,8 +39,8 @@ namespace Kyoo.Utils /// The list mapped. /// The list or the mapper can't be null [LinqTunnel] - public static IEnumerable Map([NotNull] this IEnumerable self, - [NotNull] Func mapper) + public static IEnumerable Map(this IEnumerable self, + Func mapper) { if (self == null) throw new ArgumentNullException(nameof(self)); @@ -72,8 +72,8 @@ namespace Kyoo.Utils /// The list mapped as an AsyncEnumerable. /// The list or the mapper can't be null. [LinqTunnel] - public static IAsyncEnumerable MapAsync([NotNull] this IEnumerable self, - [NotNull] Func> mapper) + public static IAsyncEnumerable MapAsync(this IEnumerable self, + Func> mapper) { if (self == null) throw new ArgumentNullException(nameof(self)); @@ -105,8 +105,8 @@ namespace Kyoo.Utils /// The list mapped as an AsyncEnumerable /// The list or the mapper can't be null [LinqTunnel] - public static IAsyncEnumerable SelectAsync([NotNull] this IEnumerable self, - [NotNull] Func> mapper) + public static IAsyncEnumerable SelectAsync(this IEnumerable self, + Func> mapper) { if (self == null) throw new ArgumentNullException(nameof(self)); @@ -132,7 +132,7 @@ namespace Kyoo.Utils /// A task that will return a simple list /// The list can't be null [LinqTunnel] - public static Task> ToListAsync([NotNull] this IAsyncEnumerable self) + public static Task> ToListAsync(this IAsyncEnumerable self) { if (self == null) throw new ArgumentNullException(nameof(self)); @@ -157,7 +157,7 @@ namespace Kyoo.Utils /// The iterable and the action can't be null. /// The iterator proxied, there is no dual iterations. [LinqTunnel] - public static IEnumerable IfEmpty([NotNull] this IEnumerable self, [NotNull] Action action) + public static IEnumerable IfEmpty(this IEnumerable self, Action action) { if (self == null) throw new ArgumentNullException(nameof(self)); @@ -190,7 +190,7 @@ namespace Kyoo.Utils /// The list to enumerate. If this is null, the function result in a no-op /// The action to execute for each arguments /// The type of items in the list - public static void ForEach([CanBeNull] this IEnumerable self, Action action) + public static void ForEach(this IEnumerable? self, Action action) { if (self == null) return; @@ -203,7 +203,7 @@ namespace Kyoo.Utils /// /// The list to enumerate. If this is null, the function result in a no-op /// The action to execute for each arguments - public static void ForEach([CanBeNull] this IEnumerable self, Action action) + public static void ForEach(this IEnumerable? self, Action action) { if (self == null) return; @@ -217,7 +217,7 @@ namespace Kyoo.Utils /// The list to enumerate. If this is null, the function result in a no-op /// The action to execute for each arguments /// A representing the asynchronous operation. - public static async Task ForEachAsync([CanBeNull] this IEnumerable self, Func action) + public static async Task ForEachAsync(this IEnumerable? self, Func action) { if (self == null) return; @@ -232,7 +232,7 @@ namespace Kyoo.Utils /// The asynchronous action to execute for each arguments /// The type of items in the list. /// A representing the asynchronous operation. - public static async Task ForEachAsync([CanBeNull] this IEnumerable self, Func action) + public static async Task ForEachAsync(this IEnumerable? self, Func action) { if (self == null) return; @@ -247,7 +247,7 @@ namespace Kyoo.Utils /// The action to execute for each arguments /// The type of items in the list. /// A representing the asynchronous operation. - public static async Task ForEachAsync([CanBeNull] this IAsyncEnumerable self, Action action) + public static async Task ForEachAsync(this IAsyncEnumerable? self, Action action) { if (self == null) return; diff --git a/back/src/Kyoo.Abstractions/Utility/Merger.cs b/back/src/Kyoo.Abstractions/Utility/Merger.cs index 237eff06..951f379b 100644 --- a/back/src/Kyoo.Abstractions/Utility/Merger.cs +++ b/back/src/Kyoo.Abstractions/Utility/Merger.cs @@ -42,9 +42,9 @@ namespace Kyoo.Utils /// The type of items in the lists to merge. /// The two list merged as an array [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static T[] MergeLists([CanBeNull] IEnumerable first, - [CanBeNull] IEnumerable second, - [CanBeNull] Func isEqual = null) + public static T[] MergeLists(IEnumerable? first, + IEnumerable? second, + Func? isEqual = null) { if (first == null) return second?.ToArray(); @@ -66,8 +66,8 @@ namespace Kyoo.Utils /// The first dictionary with the missing elements of . /// [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static IDictionary MergeDictionaries([CanBeNull] IDictionary first, - [CanBeNull] IDictionary second) + public static IDictionary MergeDictionaries(IDictionary? first, + IDictionary? second) { return MergeDictionaries(first, second, out bool _); } @@ -84,8 +84,8 @@ namespace Kyoo.Utils /// The type of values in the dictionaries /// The first dictionary with the missing elements of . [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static IDictionary MergeDictionaries([CanBeNull] IDictionary first, - [CanBeNull] IDictionary second, + public static IDictionary MergeDictionaries(IDictionary? first, + IDictionary? second, out bool hasChanged) { if (first == null) @@ -138,8 +138,8 @@ namespace Kyoo.Utils /// set to those of . /// [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static IDictionary CompleteDictionaries([CanBeNull] IDictionary first, - [CanBeNull] IDictionary second, + public static IDictionary CompleteDictionaries(IDictionary? first, + IDictionary? second, out bool hasChanged) { if (first == null) @@ -157,32 +157,6 @@ namespace Kyoo.Utils return second; } - /// - /// Set every fields of first to those of second. Ignore fields marked with the attribute - /// At the end, the OnMerge method of first will be called if first is a - /// - /// The object to assign - /// The object containing new values - /// Fields of T will be used - /// - public static T Assign(T first, T second) - { - Type type = typeof(T); - IEnumerable properties = type.GetProperties() - .Where(x => x.CanRead && x.CanWrite - && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); - - foreach (PropertyInfo property in properties) - { - object value = property.GetValue(second); - property.SetValue(first, value); - } - - if (first is IOnMerge merge) - merge.OnMerge(second); - return first; - } - /// /// Set every non-default values of seconds to the corresponding property of second. /// Dictionaries are handled like anonymous objects with a property per key/pair value @@ -210,8 +184,8 @@ namespace Kyoo.Utils /// /// If first is null public static T Complete([NotNull] T first, - [CanBeNull] T second, - [InstantHandle] Func where = null) + T? second, + [InstantHandle] Func? where = null) { if (first == null) throw new ArgumentNullException(nameof(first)); @@ -281,9 +255,9 @@ namespace Kyoo.Utils /// Fields of T will be merged /// [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static T Merge([CanBeNull] T first, - [CanBeNull] T second, - [InstantHandle] Func where = null) + public static T Merge(T? first, + T? second, + [InstantHandle] Func? where = null) { if (first == null) return second; diff --git a/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs b/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs index 69da84cc..42c01027 100644 --- a/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs +++ b/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs @@ -77,8 +77,7 @@ namespace Kyoo.Utils /// The initial task /// The type that the task will return /// A non-null task. - [NotNull] - public static Task DefaultIfNull([CanBeNull] Task value) + public static Task DefaultIfNull(Task? value) { return value ?? Task.FromResult(default); } diff --git a/back/src/Kyoo.Abstractions/Utility/Utility.cs b/back/src/Kyoo.Abstractions/Utility/Utility.cs index bce40f4e..57e261df 100644 --- a/back/src/Kyoo.Abstractions/Utility/Utility.cs +++ b/back/src/Kyoo.Abstractions/Utility/Utility.cs @@ -63,34 +63,12 @@ namespace Kyoo.Utils return member!.Member.Name; } - /// - /// Get the value of a member (property or field) - /// - /// The member value - /// The owner of this member - /// The value boxed as an object - /// if or is null. - /// The member is not a field or a property. - public static object GetValue([NotNull] this MemberInfo member, [NotNull] object obj) - { - if (member == null) - throw new ArgumentNullException(nameof(member)); - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - return member switch - { - PropertyInfo property => property.GetValue(obj), - FieldInfo field => field.GetValue(obj), - _ => throw new ArgumentException($"Can't get value of a non property/field (member: {member}).") - }; - } - /// /// Slugify a string (Replace spaces by -, Uniformize accents) /// /// The string to slugify /// The slug version of the given string - public static string ToSlug([CanBeNull] string str) + public static string ToSlug(string? str) { if (str == null) return null; @@ -132,7 +110,7 @@ namespace Kyoo.Utils /// The starting type /// A list of types /// can't be null - public static IEnumerable GetInheritanceTree([NotNull] this Type type) + public static IEnumerable GetInheritanceTree(this Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -140,20 +118,6 @@ namespace Kyoo.Utils yield return type; } - /// - /// Check if inherit from a generic type . - /// - /// Does this object's type is a - /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). - /// True if obj inherit from genericType. False otherwise - /// obj and genericType can't be null - public static bool IsOfGenericType([NotNull] object obj, [NotNull] Type genericType) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - return IsOfGenericType(obj.GetType(), genericType); - } - /// /// Check if inherit from a generic type . /// @@ -161,7 +125,7 @@ namespace Kyoo.Utils /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). /// True if obj inherit from genericType. False otherwise /// obj and genericType can't be null - public static bool IsOfGenericType([NotNull] Type type, [NotNull] Type genericType) + public static bool IsOfGenericType(Type type, Type genericType) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -186,7 +150,7 @@ namespace Kyoo.Utils /// The generic definition of genericType that type inherit or null if type does not implement the generic type. /// and can't be null /// must be a generic type - public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType) + public static Type GetGenericDefinition(Type type, Type genericType) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -224,12 +188,11 @@ namespace Kyoo.Utils /// No method match the given constraints. /// The method handle of the matching method. [PublicAPI] - [NotNull] - public static MethodInfo GetMethod([NotNull] Type type, + public static MethodInfo GetMethod(Type type, BindingFlags flag, string name, - [NotNull] Type[] generics, - [NotNull] object[] args) + Type[] generics, + object[] args) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -297,9 +260,9 @@ namespace Kyoo.Utils /// /// public static T RunGenericMethod( - [NotNull] Type owner, - [NotNull] string methodName, - [NotNull] Type type, + Type owner, + string methodName, + Type type, params object[] args) { return RunGenericMethod(owner, methodName, new[] { type }, args); @@ -334,9 +297,9 @@ namespace Kyoo.Utils /// [PublicAPI] public static T RunGenericMethod( - [NotNull] Type owner, - [NotNull] string methodName, - [NotNull] Type[] types, + Type owner, + string methodName, + Type[] types, params object[] args) { if (owner == null) @@ -351,83 +314,6 @@ namespace Kyoo.Utils return (T)method.MakeGenericMethod(types).Invoke(null, args); } - /// - /// Run a generic method for a runtime . - /// - /// - /// To run for a List where you don't know the type at compile type, - /// you could do: - /// - /// Utility.RunGenericMethod<object>( - /// typeof(Utility), - /// nameof(MergeLists), - /// enumerableType, - /// oldValue, newValue, equalityComparer) - /// - /// - /// The this of the method to run. - /// The name of the method. You should use the nameof keyword. - /// The generic type to run the method with. - /// The list of arguments of the method - /// - /// The return type of the method. You can put for an unknown one. - /// - /// No method match the given constraints. - /// The return of the method you wanted to run. - /// - /// - public static T RunGenericMethod( - [NotNull] object instance, - [NotNull] string methodName, - [NotNull] Type type, - params object[] args) - { - return RunGenericMethod(instance, methodName, new[] { type }, args); - } - - /// - /// Run a generic method for a multiple runtime . - /// If your generic method only needs one type, see - /// - /// - /// - /// To run for a List where you don't know the type at compile type, - /// you could do: - /// - /// Utility.RunGenericMethod<object>( - /// typeof(Utility), - /// nameof(MergeLists), - /// enumerableType, - /// oldValue, newValue, equalityComparer) - /// - /// - /// The this of the method to run. - /// The name of the method. You should use the nameof keyword. - /// The list of generic types to run the method with. - /// The list of arguments of the method - /// - /// The return type of the method. You can put for an unknown one. - /// - /// No method match the given constraints. - /// The return of the method you wanted to run. - /// - /// - public static T RunGenericMethod( - [NotNull] object instance, - [NotNull] string methodName, - [NotNull] Type[] types, - params object[] args) - { - if (instance == null) - throw new ArgumentNullException(nameof(instance)); - if (methodName == null) - throw new ArgumentNullException(nameof(methodName)); - if (types == null || types.Length == 0) - throw new ArgumentNullException(nameof(types)); - MethodInfo method = GetMethod(instance.GetType(), BindingFlags.Instance, methodName, types, args); - return (T)method.MakeGenericMethod(types).Invoke(instance, args.ToArray()); - } - /// /// Convert a dictionary to a query string. /// @@ -446,25 +332,11 @@ namespace Kyoo.Utils /// /// The exception to rethrow. [System.Diagnostics.CodeAnalysis.DoesNotReturn] - public static void ReThrow([NotNull] this Exception ex) + public static void ReThrow(this Exception ex) { if (ex == null) throw new ArgumentNullException(nameof(ex)); ExceptionDispatchInfo.Capture(ex).Throw(); } - - /// - /// Get a friendly type name (supporting generics) - /// For example a list of string will be displayed as List<string> and not as List`1. - /// - /// The type to use - /// The friendly name of the type - public static string FriendlyName(this Type type) - { - if (!type.IsGenericType) - return type.Name; - string generics = string.Join(", ", type.GetGenericArguments().Select(x => x.FriendlyName())); - return $"{type.Name[..type.Name.IndexOf('`')]}<{generics}>"; - } } } diff --git a/back/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs b/back/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs index e8e76750..394cbf74 100644 --- a/back/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs +++ b/back/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs @@ -72,7 +72,6 @@ namespace Kyoo.Authentication.Models.DTO Username = Username, Password = BCryptNet.HashPassword(Password), Email = Email, - ExtraData = new Dictionary() }; } } diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs index edced1e7..6ce6857e 100644 --- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -39,15 +39,15 @@ namespace Kyoo.Core.Controllers /// private readonly IBaseRepository[] _repositories; - /// - public ILibraryRepository LibraryRepository { get; } - /// public ILibraryItemRepository LibraryItemRepository { get; } /// public ICollectionRepository CollectionRepository { get; } + /// + public IMovieRepository MovieRepository { get; } + /// public IShowRepository ShowRepository { get; } @@ -63,9 +63,6 @@ namespace Kyoo.Core.Controllers /// public IStudioRepository StudioRepository { get; } - /// - public IGenreRepository GenreRepository { get; } - /// public IUserRepository UserRepository { get; } @@ -77,15 +74,14 @@ namespace Kyoo.Core.Controllers public LibraryManager(IEnumerable repositories) { _repositories = repositories.ToArray(); - LibraryRepository = GetRepository() as ILibraryRepository; - LibraryItemRepository = GetRepository() as ILibraryItemRepository; + LibraryItemRepository = GetRepository() as ILibraryItemRepository; CollectionRepository = GetRepository() as ICollectionRepository; + MovieRepository = GetRepository() as IMovieRepository; ShowRepository = GetRepository() as IShowRepository; SeasonRepository = GetRepository() as ISeasonRepository; EpisodeRepository = GetRepository() as IEpisodeRepository; PeopleRepository = GetRepository() as IPeopleRepository; StudioRepository = GetRepository() as IStudioRepository; - GenreRepository = GetRepository() as IGenreRepository; UserRepository = GetRepository() as IUserRepository; } @@ -255,28 +251,10 @@ namespace Kyoo.Core.Controllers return (obj, member: memberName) switch { - (Library l, nameof(Library.Shows)) => ShowRepository - .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) - .Then(x => l.Shows = x), - - (Library l, nameof(Library.Collections)) => CollectionRepository - .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) - .Then(x => l.Collections = x), - - (Collection c, nameof(Collection.Shows)) => ShowRepository .GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) .Then(x => c.Shows = x), - (Collection c, nameof(Collection.Libraries)) => LibraryRepository - .GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) - .Then(x => c.Libraries = x), - - - (Show s, nameof(Show.Genres)) => GenreRepository - .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) - .Then(x => s.Genres = x), - (Show s, nameof(Show.People)) => PeopleRepository .GetFromShow(obj.ID) .Then(x => s.People = x), @@ -291,10 +269,6 @@ namespace Kyoo.Core.Controllers (x, y) => x.Episodes = y, (x, y) => { x.Show = y; x.ShowID = y.ID; }), - (Show s, nameof(Show.Libraries)) => LibraryRepository - .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) - .Then(x => s.Libraries = x), - (Show s, nameof(Show.Collections)) => CollectionRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Collections = x), @@ -339,11 +313,6 @@ namespace Kyoo.Core.Controllers }), - (Genre g, nameof(Genre.Shows)) => ShowRepository - .GetAll(x => x.Genres.Any(y => y.ID == obj.ID)) - .Then(x => g.Shows = x), - - (Studio s, nameof(Studio.Shows)) => ShowRepository .GetAll(x => x.Studio.ID == obj.ID) .Then(x => s.Shows = x), @@ -356,24 +325,6 @@ namespace Kyoo.Core.Controllers }; } - /// - public Task> GetItemsFromLibrary(int id, - Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return LibraryItemRepository.GetFromLibrary(id, where, sort, limit); - } - - /// - public Task> GetItemsFromLibrary(string slug, - Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return LibraryItemRepository.GetFromLibrary(slug, where, sort, limit); - } - /// public Task> GetPeopleFromShow(int showID, Expression> where = null, @@ -410,20 +361,6 @@ namespace Kyoo.Core.Controllers return PeopleRepository.GetFromPeople(slug, where, sort, limit); } - /// - public Task AddShowLink(int showID, int? libraryID, int? collectionID) - { - return ShowRepository.AddShowLink(showID, libraryID, collectionID); - } - - /// - public Task AddShowLink(Show show, Library library, Collection collection) - { - if (show == null) - throw new ArgumentNullException(nameof(show)); - return ShowRepository.AddShowLink(show.ID, library?.ID, collection?.ID); - } - /// public Task> GetAll(Expression> where = null, Sort sort = default, diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index eb58fe98..489865ac 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -129,7 +129,7 @@ namespace Kyoo.Core.Controllers _database.Episodes .Include(x => x.Show) .Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null) - .Where(_database.Like(x => x.Title, $"%{query}%")) + .Where(_database.Like(x => x.Name, $"%{query}%")) ) .Take(20) .ToListAsync(); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs deleted file mode 100644 index 73c05a68..00000000 --- a/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs +++ /dev/null @@ -1,93 +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.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Kyoo.Postgresql; -using Kyoo.Utils; -using Microsoft.EntityFrameworkCore; - -namespace Kyoo.Core.Controllers -{ - /// - /// A local repository for genres. - /// - public class GenreRepository : LocalRepository, IGenreRepository - { - /// - /// The database handle - /// - private readonly DatabaseContext _database; - - /// - protected override Sort DefaultSort => new Sort.By(x => x.Slug); - - /// - /// Create a new . - /// - /// The database handle - public GenreRepository(DatabaseContext database) - : base(database) - { - _database = database; - } - - /// - public override async Task> Search(string query) - { - return await Sort( - _database.Genres - .Where(_database.Like(x => x.Name, $"%{query}%")) - ) - .Take(20) - .ToListAsync(); - } - - /// - protected override async Task Validate(Genre resource) - { - resource.Slug ??= Utility.ToSlug(resource.Name); - await base.Validate(resource); - } - - /// - public override async Task Create(Genre 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(Genre obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - _database.Entry(obj).State = EntityState.Deleted; - await _database.SaveChangesAsync(); - await base.Delete(obj); - } - } -} diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index a9b15fb5..d8325a51 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -23,7 +23,6 @@ using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Postgresql; using Microsoft.EntityFrameworkCore; @@ -32,84 +31,80 @@ namespace Kyoo.Core.Controllers /// /// A local repository to handle library items. /// - public class LibraryItemRepository : LocalRepository, ILibraryItemRepository + public class LibraryItemRepository : LocalRepository, ILibraryItemRepository { /// /// The database handle /// private readonly DatabaseContext _database; - /// - /// A lazy loaded library repository to validate queries (check if a library does exist) - /// - private readonly Lazy _libraries; - /// - protected override Sort DefaultSort => new Sort.By(x => x.Title); + protected override Sort DefaultSort => new Sort.By(x => x.Name); /// - /// Create a new . + /// Create a new . /// /// The database instance - /// A lazy loaded library repository - public LibraryItemRepository(DatabaseContext database, - Lazy libraries) + public LibraryItemRepository(DatabaseContext database) : base(database) { _database = database; - _libraries = libraries; } /// - public override Task GetOrDefault(int id) + public override async Task GetOrDefault(int id) { - return _database.LibraryItems.FirstOrDefaultAsync(x => x.ID == id); + return (await _database.LibraryItems.FirstOrDefaultAsync(x => x.ID == id)).ToItem(); } /// - public override Task GetOrDefault(string slug) + public override async Task GetOrDefault(string slug) { - return _database.LibraryItems.SingleOrDefaultAsync(x => x.Slug == slug); + return (await _database.LibraryItems.SingleOrDefaultAsync(x => x.Slug == slug)).ToItem(); } /// - public override Task> GetAll(Expression> where = null, - Sort sort = default, + public override async Task> GetAll(Expression> where = null, + Sort sort = default, Pagination limit = default) { - return ApplyFilters(_database.LibraryItems, where, sort, limit); + return (await ApplyFilters(_database.LibraryItems, where, sort, limit)) + .Select(x => (x as BagItem)!.ToItem()) + .ToList(); } /// - public override Task GetCount(Expression> where = null) + public override Task GetCount(Expression> where = null) { - IQueryable query = _database.LibraryItems; + IQueryable query = _database.LibraryItems; if (where != null) query = query.Where(where); return query.CountAsync(); } /// - public override async Task> Search(string query) + public override async Task> Search(string query) { - return await Sort( - _database.LibraryItems - .Where(_database.Like(x => x.Title, $"%{query}%")) + return (await Sort( + _database.LibraryItems + .Where(_database.Like(x => x.Name, $"%{query}%")) ) .Take(20) - .ToListAsync(); + .ToListAsync()) + .Select(x => (x as BagItem)!.ToItem()) + .ToList(); } /// - public override Task Create(LibraryItem obj) + public override Task Create(ILibraryItem obj) => throw new InvalidOperationException(); /// - public override Task CreateIfNotExists(LibraryItem obj) + public override Task CreateIfNotExists(ILibraryItem obj) => throw new InvalidOperationException(); /// - public override Task Edit(LibraryItem obj, bool resetOld) + public override Task Edit(ILibraryItem obj, bool resetOld) => throw new InvalidOperationException(); /// @@ -121,58 +116,7 @@ namespace Kyoo.Core.Controllers => throw new InvalidOperationException(); /// - public override Task Delete(LibraryItem obj) + public override Task Delete(ILibraryItem obj) => throw new InvalidOperationException(); - - /// - /// Get a basic queryable for a library with the right mapping from shows and collections. - /// Shows contained in a collection are excluded. - /// - /// Only items that are part of a library that match this predicate will be returned. - /// A queryable containing items that are part of a library matching the selector. - private IQueryable _LibraryRelatedQuery(Expression> selector) - => _database.Libraries - .Where(selector) - .SelectMany(x => x.Shows) - .Where(x => !x.Collections.Any()) - .Select(LibraryItem.FromShow) - .Concat(_database.Libraries - .Where(selector) - .SelectMany(x => x.Collections) - .Select(LibraryItem.FromCollection)); - - /// - public async Task> GetFromLibrary(int id, - Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - ICollection items = await ApplyFilters( - _LibraryRelatedQuery(x => x.ID == id), - where, - sort, - limit - ); - if (!items.Any() && await _libraries.Value.GetOrDefault(id) == null) - throw new ItemNotFoundException(); - return items; - } - - /// - public async Task> GetFromLibrary(string slug, - Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - ICollection items = await ApplyFilters( - _LibraryRelatedQuery(x => x.Slug == slug), - where, - sort, - limit - ); - if (!items.Any() && await _libraries.Value.GetOrDefault(slug) == null) - throw new ItemNotFoundException(); - return items; - } } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs deleted file mode 100644 index 59e7083c..00000000 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs +++ /dev/null @@ -1,99 +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.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Kyoo.Postgresql; -using Kyoo.Utils; -using Microsoft.EntityFrameworkCore; - -namespace Kyoo.Core.Controllers -{ - /// - /// A local repository to handle libraries. - /// - public class LibraryRepository : LocalRepository, ILibraryRepository - { - /// - /// The database handle - /// - private readonly DatabaseContext _database; - - /// - protected override Sort DefaultSort => new Sort.By(x => x.ID); - - /// - /// Create a new instance. - /// - /// The database handle - public LibraryRepository(DatabaseContext database) - : base(database) - { - _database = database; - } - - /// - public override async Task> Search(string query) - { - return await Sort( - _database.Libraries - .Where(_database.Like(x => x.Name + " " + x.Slug, $"%{query}%")) - ) - .Take(20) - .ToListAsync(); - } - - /// - public override async Task Create(Library obj) - { - await base.Create(obj); - _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync(() => Get(obj.Slug)); - OnResourceCreated(obj); - return obj; - } - - /// - protected override async Task Validate(Library resource) - { - await base.Validate(resource); - - if (string.IsNullOrEmpty(resource.Slug)) - throw new ArgumentException("The library's slug must be set and not empty"); - if (string.IsNullOrEmpty(resource.Name)) - throw new ArgumentException("The library's name must be set and not empty"); - if (resource.Paths == null || !resource.Paths.Any()) - throw new ArgumentException("The library should have a least one path."); - } - - /// - public override async Task Delete(Library obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - _database.Entry(obj).State = EntityState.Deleted; - await _database.SaveChangesAsync(); - await base.Delete(obj); - } - } -} diff --git a/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs new file mode 100644 index 00000000..1b6fe33a --- /dev/null +++ b/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs @@ -0,0 +1,139 @@ +// 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 System.Linq; +using System.Threading.Tasks; +using Kyoo.Abstractions.Controllers; +using Kyoo.Abstractions.Models; +using Kyoo.Postgresql; +using Kyoo.Utils; +using Microsoft.EntityFrameworkCore; + +namespace Kyoo.Core.Controllers +{ + /// + /// A local repository to handle shows + /// + public class MovieRepository : LocalRepository, IMovieRepository + { + /// + /// The database handle + /// + private readonly DatabaseContext _database; + + /// + /// A studio repository to handle creation/validation of related studios. + /// + private readonly IStudioRepository _studios; + + /// + /// A people repository to handle creation/validation of related people. + /// + private readonly IPeopleRepository _people; + + /// + protected override Sort DefaultSort => new Sort.By(x => x.Name); + + /// + /// Create a new . + /// + /// The database handle to use + /// A studio repository + /// A people repository + public MovieRepository(DatabaseContext database, + IStudioRepository studios, + IPeopleRepository people) + : base(database) + { + _database = database; + _studios = studios; + _people = people; + } + + /// + public override async Task> Search(string query) + { + query = $"%{query}%"; + return await Sort( + _database.Movies + .Where(_database.Like(x => x.Name + " " + x.Slug, query)) + ) + .Take(20) + .ToListAsync(); + } + + /// + public override async Task Create(Movie obj) + { + await base.Create(obj); + _database.Entry(obj).State = EntityState.Added; + await _database.SaveChangesAsync(() => Get(obj.Slug)); + OnResourceCreated(obj); + return obj; + } + + /// + protected override async Task Validate(Movie resource) + { + await base.Validate(resource); + if (resource.Studio != null) + { + resource.Studio = await _studios.CreateIfNotExists(resource.Studio); + resource.StudioID = resource.Studio.ID; + } + + if (resource.People != null) + { + foreach (PeopleRole role in resource.People) + { + role.People = _database.LocalEntity(role.People.Slug) + ?? await _people.CreateIfNotExists(role.People); + role.PeopleID = role.People.ID; + _database.Entry(role).State = EntityState.Added; + } + } + } + + /// + protected override async Task EditRelations(Movie resource, Movie changed, bool resetOld) + { + await Validate(changed); + + if (changed.Studio != null || resetOld) + { + await Database.Entry(resource).Reference(x => x.Studio).LoadAsync(); + resource.Studio = changed.Studio; + } + + if (changed.People != null || resetOld) + { + await Database.Entry(resource).Collection(x => x.People).LoadAsync(); + resource.People = changed.People; + } + } + + /// + public override async Task Delete(Movie obj) + { + _database.Remove(obj); + await _database.SaveChangesAsync(); + await base.Delete(obj); + } + } +} diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 7b12cf73..104b2367 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -102,7 +102,7 @@ namespace Kyoo.Core.Controllers { return await Sort( _database.Seasons - .Where(_database.Like(x => x.Title, $"%{query}%")) + .Where(_database.Like(x => x.Name, $"%{query}%")) ) .Take(20) .ToListAsync(); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index 4e2092b2..32e378ff 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -47,13 +47,8 @@ namespace Kyoo.Core.Controllers /// private readonly IPeopleRepository _people; - /// - /// A genres repository to handle creation/validation of related genres. - /// - private readonly IGenreRepository _genres; - /// - protected override Sort DefaultSort => new Sort.By(x => x.Title); + protected override Sort DefaultSort => new Sort.By(x => x.Name); /// /// Create a new . @@ -61,17 +56,14 @@ namespace Kyoo.Core.Controllers /// The database handle to use /// A studio repository /// A people repository - /// A genres repository public ShowRepository(DatabaseContext database, IStudioRepository studios, - IPeopleRepository people, - IGenreRepository genres) + IPeopleRepository people) : base(database) { _database = database; _studios = studios; _people = people; - _genres = genres; } /// @@ -80,7 +72,7 @@ namespace Kyoo.Core.Controllers query = $"%{query}%"; return await Sort( _database.Shows - .Where(_database.Like(x => x.Title + " " + x.Slug, query)) + .Where(_database.Like(x => x.Name + " " + x.Slug, query)) ) .Take(20) .ToListAsync(); @@ -99,7 +91,7 @@ namespace Kyoo.Core.Controllers /// protected override async Task Validate(Show resource) { - resource.Slug ??= Utility.ToSlug(resource.Title); + resource.Slug ??= Utility.ToSlug(resource.Name); await base.Validate(resource); if (resource.Studio != null) @@ -108,14 +100,6 @@ namespace Kyoo.Core.Controllers resource.StudioID = resource.Studio.ID; } - if (resource.Genres != null) - { - resource.Genres = await resource.Genres - .SelectAsync(x => _genres.CreateIfNotExists(x)) - .ToListAsync(); - _database.AttachRange(resource.Genres); - } - if (resource.People != null) { foreach (PeopleRole role in resource.People) @@ -133,21 +117,12 @@ namespace Kyoo.Core.Controllers { await Validate(changed); - if (changed.Aliases != null || resetOld) - resource.Aliases = changed.Aliases; - if (changed.Studio != null || resetOld) { await Database.Entry(resource).Reference(x => x.Studio).LoadAsync(); resource.Studio = changed.Studio; } - if (changed.Genres != null || resetOld) - { - await Database.Entry(resource).Collection(x => x.Genres).LoadAsync(); - resource.Genres = changed.Genres; - } - if (changed.People != null || resetOld) { await Database.Entry(resource).Collection(x => x.People).LoadAsync(); @@ -155,27 +130,6 @@ namespace Kyoo.Core.Controllers } } - /// - public async Task AddShowLink(int showID, int? libraryID, int? collectionID) - { - if (collectionID != null) - { - await _database.AddLinks(collectionID.Value, showID); - await _database.SaveIfNoDuplicates(); - - if (libraryID != null) - { - await _database.AddLinks(libraryID.Value, collectionID.Value); - await _database.SaveIfNoDuplicates(); - } - } - if (libraryID != null) - { - await _database.AddLinks(libraryID.Value, showID); - await _database.SaveIfNoDuplicates(); - } - } - /// public Task GetSlug(int showID) { diff --git a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs index 4d561b2a..652380d2 100644 --- a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -98,9 +98,9 @@ namespace Kyoo.Core.Controllers throw new ArgumentNullException(nameof(item)); string name = item is IResource res ? res.Slug : "???"; - 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}"); + 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}"); } private static string _GetBaseImagePath(T item, string image) diff --git a/back/src/Kyoo.Core/CoreModule.cs b/back/src/Kyoo.Core/CoreModule.cs index 162ce4bc..ff6e9535 100644 --- a/back/src/Kyoo.Core/CoreModule.cs +++ b/back/src/Kyoo.Core/CoreModule.cs @@ -30,6 +30,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using JsonOptions = Kyoo.Core.Api.JsonOptions; namespace Kyoo.Core @@ -48,15 +49,14 @@ namespace Kyoo.Core builder.RegisterType().As().InstancePerLifetimeScope(); builder.RegisterType().As().InstancePerLifetimeScope(); - builder.RegisterRepository(); builder.RegisterRepository(); builder.RegisterRepository(); + builder.RegisterRepository(); builder.RegisterRepository(); builder.RegisterRepository(); builder.RegisterRepository(); builder.RegisterRepository(); builder.RegisterRepository(); - builder.RegisterRepository(); builder.RegisterRepository(); } @@ -73,6 +73,7 @@ namespace Kyoo.Core .AddNewtonsoftJson(x => { x.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; + x.SerializerSettings.Converters.Add(new StringEnumConverter()); }) .AddDataAnnotations() .AddControllersAsServices() diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Library.cs b/back/src/Kyoo.Core/Views/Helper/Serializers/ImageConvertor.cs similarity index 50% rename from back/src/Kyoo.Abstractions/Models/Resources/Library.cs rename to back/src/Kyoo.Core/Views/Helper/Serializers/ImageConvertor.cs index 0c4129f4..52674995 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Library.cs +++ b/back/src/Kyoo.Core/Views/Helper/Serializers/ImageConvertor.cs @@ -16,40 +16,31 @@ // 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 Kyoo.Abstractions.Models.Attributes; +using Kyoo.Abstractions.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; -namespace Kyoo.Abstractions.Models +namespace Kyoo.Core.Api { - /// - /// A library containing and . - /// - public class Library : IResource + public class ImageConverter : JsonConverter { /// - public int ID { get; set; } + public override void WriteJson(JsonWriter writer, Image value, JsonSerializer serializer) + { + JObject obj = JObject.FromObject(value, serializer); + obj.WriteTo(writer); + } /// - public string Slug { get; set; } - - /// - /// The name of this library. - /// - public string Name { get; set; } - - /// - /// The list of paths that this library is responsible for. This is mainly used by the Scan task. - /// - public string[] Paths { get; set; } - - /// - /// The list of shows in this library. - /// - [LoadableRelation] public ICollection Shows { get; set; } - - /// - /// The list of collections in this library. - /// - [LoadableRelation] public ICollection Collections { get; set; } + public override Image ReadJson(JsonReader reader, + Type objectType, + Image existingValue, + bool hasExistingValue, + JsonSerializer serializer) + { + throw new NotImplementedException(); + } } } diff --git a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs index c0b1d8bb..98429630 100644 --- a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs +++ b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Reflection; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Attributes; diff --git a/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs b/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs deleted file mode 100644 index eb72b284..00000000 --- a/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs +++ /dev/null @@ -1,95 +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 System.Linq; -using System.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Abstractions.Models.Permissions; -using Kyoo.Abstractions.Models.Utils; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using static Kyoo.Abstractions.Models.Utils.Constants; - -namespace Kyoo.Core.Api -{ - /// - /// Information about one or multiple . - /// - [Route("genres")] - [Route("genre", Order = AlternativeRoute)] - [ApiController] - [PartialPermission(nameof(Genre))] - [ApiDefinition("Genres", Group = MetadataGroup)] - public class GenreApi : CrudApi - { - /// - /// The library manager used to modify or retrieve information about the data store. - /// - private readonly ILibraryManager _libraryManager; - - /// - /// Create a new . - /// - /// - /// The library manager used to modify or retrieve information about the data store. - /// - public GenreApi(ILibraryManager libraryManager) - : base(libraryManager.GenreRepository) - { - _libraryManager = libraryManager; - } - - /// - /// Get shows with genre - /// - /// - /// Lists the shows that have the selected genre. - /// - /// The ID or slug of the . - /// A key to sort shows by. - /// An optional list of filters. - /// The number of shows to return and where to start. - /// A page of shows. - /// The filters or the sort parameters are invalid. - /// No genre with the given ID could be found. - [HttpGet("{identifier:id}/shows")] - [HttpGet("{identifier:id}/show", Order = AlternativeRoute)] - [PartialPermission(Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetShows(Identifier identifier, - [FromQuery] string sortBy, - [FromQuery] Dictionary where, - [FromQuery] Pagination pagination) - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Genres)), - Sort.From(sortBy), - pagination - ); - - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, pagination.Limit); - } - } -} diff --git a/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs b/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs index ffd13752..bc15368a 100644 --- a/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs @@ -93,40 +93,5 @@ namespace Kyoo.Core.Api return NotFound(); return Page(resources, pagination.Limit); } - - /// - /// Get libraries containing this collection - /// - /// - /// Lists the libraries that contain the collection with the given id or slug. - /// - /// The ID or slug of the . - /// A key to sort libraries by. - /// An optional list of filters. - /// The number of libraries to return. - /// A page of libraries. - /// The filters or the sort parameters are invalid. - /// No collection with the given ID or slug could be found. - [HttpGet("{identifier:id}/libraries")] - [HttpGet("{identifier:id}/library", Order = AlternativeRoute)] - [PartialPermission(Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetLibraries(Identifier identifier, - [FromQuery] string sortBy, - [FromQuery] Dictionary where, - [FromQuery] Pagination pagination) - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Collections)), - Sort.From(sortBy), - pagination - ); - - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, pagination.Limit); - } } } diff --git a/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs b/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs deleted file mode 100644 index 6551a99a..00000000 --- a/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs +++ /dev/null @@ -1,171 +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.Abstractions.Models.Attributes; -using Kyoo.Abstractions.Models.Permissions; -using Kyoo.Abstractions.Models.Utils; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using static Kyoo.Abstractions.Models.Utils.Constants; - -namespace Kyoo.Core.Api -{ - /// - /// Information about one or multiple . - /// - [Route("libraries")] - [Route("library", Order = AlternativeRoute)] - [ApiController] - [ResourceView] - [PartialPermission(nameof(Library), Group = Group.Admin)] - [ApiDefinition("Library", Group = ResourcesGroup)] - public class LibraryApi : CrudApi - { - /// - /// The library manager used to modify or retrieve information in the data store. - /// - private readonly ILibraryManager _libraryManager; - - /// - /// Create a new . - /// - /// - /// The library manager used to modify or retrieve information in the data store. - /// - public LibraryApi(ILibraryManager libraryManager) - : base(libraryManager.LibraryRepository) - { - _libraryManager = libraryManager; - } - - /// - /// Get shows - /// - /// - /// List the shows that are part of this library. - /// - /// The ID or slug of the . - /// A key to sort shows by. - /// An optional list of filters. - /// The number of shows to return. - /// A page of shows. - /// The filters or the sort parameters are invalid. - /// No library with the given ID or slug could be found. - [HttpGet("{identifier:id}/shows")] - [HttpGet("{identifier:id}/show", Order = AlternativeRoute)] - [PartialPermission(Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetShows(Identifier identifier, - [FromQuery] string sortBy, - [FromQuery] Dictionary where, - [FromQuery] Pagination pagination) - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), - Sort.From(sortBy), - pagination - ); - - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, pagination.Limit); - } - - /// - /// Get collections - /// - /// - /// List the collections that are part of this library. - /// - /// The ID or slug of the . - /// A key to sort collections by. - /// An optional list of filters. - /// The number of collections to return. - /// A page of collections. - /// The filters or the sort parameters are invalid. - /// No library with the given ID or slug could be found. - [HttpGet("{identifier:id}/collections")] - [HttpGet("{identifier:id}/collection", Order = AlternativeRoute)] - [PartialPermission(Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetCollections(Identifier identifier, - [FromQuery] string sortBy, - [FromQuery] Dictionary where, - [FromQuery] Pagination pagination) - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), - Sort.From(sortBy), - pagination - ); - - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, pagination.Limit); - } - - /// - /// Get items - /// - /// - /// List all items of this library. - /// An item can ether represent a collection or a show. - /// This endpoint allow one to retrieve all collections and shows that are not contained in a collection. - /// This is what is displayed on the /browse/library page of the webapp. - /// - /// The ID or slug of the . - /// A key to sort items by. - /// An optional list of filters. - /// The number of items to return. - /// A page of items. - /// The filters or the sort parameters are invalid. - /// No library with the given ID or slug could be found. - [HttpGet("{identifier:id}/items")] - [HttpGet("{identifier:id}/item", Order = AlternativeRoute)] - [PartialPermission(Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetItems(Identifier identifier, - [FromQuery] string sortBy, - [FromQuery] Dictionary where, - [FromQuery] Pagination pagination) - { - Expression> whereQuery = ApiHelper.ParseWhere(where); - Sort sort = Sort.From(sortBy); - - ICollection resources = await identifier.Match( - id => _libraryManager.GetItemsFromLibrary(id, whereQuery, sort, pagination), - slug => _libraryManager.GetItemsFromLibrary(slug, whereQuery, sort, pagination) - ); - - return Page(resources, pagination.Limit); - } - } -} diff --git a/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs b/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs index f1b6747d..8046a942 100644 --- a/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.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.Threading.Tasks; using Kyoo.Abstractions.Controllers; @@ -38,7 +37,7 @@ namespace Kyoo.Core.Api [Route("item", Order = AlternativeRoute)] [ApiController] [ResourceView] - [PartialPermission(nameof(LibraryItem))] + [PartialPermission("LibraryItem")] [ApiDefinition("Items", Group = ResourcesGroup)] public class LibraryItemApi : BaseApi { @@ -78,14 +77,14 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetAll( + public async Task>> GetAll( [FromQuery] string sortBy, [FromQuery] Dictionary where, [FromQuery] Pagination pagination) { - ICollection resources = await _libraryItems.GetAll( - ApiHelper.ParseWhere(where), - Sort.From(sortBy), + ICollection resources = await _libraryItems.GetAll( + ApiHelper.ParseWhere(where), + Sort.From(sortBy), pagination ); diff --git a/back/src/Kyoo.Core/Views/Resources/MovieApi.cs b/back/src/Kyoo.Core/Views/Resources/MovieApi.cs new file mode 100644 index 00000000..a1825517 --- /dev/null +++ b/back/src/Kyoo.Core/Views/Resources/MovieApi.cs @@ -0,0 +1,149 @@ +// 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 System.Linq; +using System.Threading.Tasks; +using Kyoo.Abstractions.Controllers; +using Kyoo.Abstractions.Models; +using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Abstractions.Models.Permissions; +using Kyoo.Abstractions.Models.Utils; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using static Kyoo.Abstractions.Models.Utils.Constants; + +namespace Kyoo.Core.Api +{ + /// + /// Information about one or multiple . + /// + [Route("movies")] + [Route("movie", Order = AlternativeRoute)] + [ApiController] + [PartialPermission(nameof(Show))] + [ApiDefinition("Shows", Group = ResourcesGroup)] + public class MovieApi : CrudThumbsApi + { + /// + /// The library manager used to modify or retrieve information in the data store. + /// + private readonly ILibraryManager _libraryManager; + + /// + /// Create a new . + /// + /// + /// The library manager used to modify or retrieve information about the data store. + /// + /// The thumbnail manager used to retrieve images paths. + public MovieApi(ILibraryManager libraryManager, + IThumbnailsManager thumbs) + : base(libraryManager.MovieRepository, thumbs) + { + _libraryManager = libraryManager; + } + + // /// + // /// Get staff + // /// + // /// + // /// List staff members that made this show. + // /// + // /// The ID or slug of the . + // /// A key to sort staff members by. + // /// An optional list of filters. + // /// The number of people to return. + // /// A page of people. + // /// The filters or the sort parameters are invalid. + // /// No show with the given ID or slug could be found. + // [HttpGet("{identifier:id}/staff")] + // [HttpGet("{identifier:id}/people", Order = AlternativeRoute)] + // [PartialPermission(Kind.Read)] + // [ProducesResponseType(StatusCodes.Status200OK)] + // [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] + // [ProducesResponseType(StatusCodes.Status404NotFound)] + // public async Task>> GetPeople(Identifier identifier, + // [FromQuery] string sortBy, + // [FromQuery] Dictionary where, + // [FromQuery] Pagination pagination) + // { + // Expression> whereQuery = ApiHelper.ParseWhere(where); + // Sort sort = Sort.From(sortBy); + // + // ICollection resources = await identifier.Match( + // id => _libraryManager.GetPeopleFromShow(id, whereQuery, sort, pagination), + // slug => _libraryManager.GetPeopleFromShow(slug, whereQuery, sort, pagination) + // ); + // return Page(resources, pagination.Limit); + // } + + /// + /// Get studio that made the show + /// + /// + /// Get the studio that made the show. + /// + /// The ID or slug of the . + /// The studio that made the show. + /// No show with the given ID or slug could be found. + [HttpGet("{identifier:id}/studio")] + [PartialPermission(Kind.Read)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetStudio(Identifier identifier) + { + return await _libraryManager.Get(identifier.IsContainedIn(x => x.Movies)); + } + + /// + /// Get collections containing this show + /// + /// + /// List the collections that contain this show. + /// + /// The ID or slug of the . + /// A key to sort collections by. + /// An optional list of filters. + /// The number of collections to return. + /// A page of collections. + /// The filters or the sort parameters are invalid. + /// No show with the given ID or slug could be found. + [HttpGet("{identifier:id}/collections")] + [HttpGet("{identifier:id}/collection", Order = AlternativeRoute)] + [PartialPermission(Kind.Read)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetCollections(Identifier identifier, + [FromQuery] string sortBy, + [FromQuery] Dictionary where, + [FromQuery] Pagination pagination) + { + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Movies)), + Sort.From(sortBy), + pagination + ); + + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Limit); + } + } +} diff --git a/back/src/Kyoo.Core/Views/Resources/SearchApi.cs b/back/src/Kyoo.Core/Views/Resources/SearchApi.cs index 79e9ec2b..5c09d896 100644 --- a/back/src/Kyoo.Core/Views/Resources/SearchApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/SearchApi.cs @@ -79,7 +79,6 @@ namespace Kyoo.Core.Api Shows = await _libraryManager.Search(query), Episodes = await _libraryManager.Search(query), People = await _libraryManager.Search(query), - Genres = await _libraryManager.Search(query), Studios = await _libraryManager.Search(query) }; } @@ -133,9 +132,9 @@ namespace Kyoo.Core.Api [Permission(nameof(Show), Kind.Read)] [ApiDefinition("Items")] [ProducesResponseType(StatusCodes.Status200OK)] - public Task> SearchItems(string query) + public Task> SearchItems(string query) { - return _libraryManager.Search(query); + return _libraryManager.Search(query); } /// @@ -175,24 +174,6 @@ namespace Kyoo.Core.Api return _libraryManager.Search(query); } - /// - /// Search genres - /// - /// - /// Search for genres - /// - /// The query to search for. - /// A list of genres found for the specified query. - [HttpGet("genres")] - [HttpGet("genre", Order = AlternativeRoute)] - [Permission(nameof(Genre), Kind.Read)] - [ApiDefinition("Genres")] - [ProducesResponseType(StatusCodes.Status200OK)] - public Task> SearchGenres(string query) - { - return _libraryManager.Search(query); - } - /// /// Search studios /// diff --git a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs index 95a9492e..68d44eb1 100644 --- a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs @@ -37,8 +37,6 @@ namespace Kyoo.Core.Api /// [Route("shows")] [Route("show", Order = AlternativeRoute)] - [Route("movie", Order = AlternativeRoute)] - [Route("movies", Order = AlternativeRoute)] [ApiController] [PartialPermission(nameof(Show))] [ApiDefinition("Shows", Group = ResourcesGroup)] @@ -63,24 +61,6 @@ namespace Kyoo.Core.Api _libraryManager = libraryManager; } - /// - public override async Task> Create([FromBody] Show resource) - { - ActionResult ret = await base.Create(resource); - if (ret.Value.IsMovie) - { - Episode episode = new() - { - Show = ret.Value, - Title = ret.Value.Title, - Path = ret.Value.Path - }; - - await _libraryManager.Create(episode); - } - return ret; - } - /// /// Get seasons of this show /// @@ -185,41 +165,6 @@ namespace Kyoo.Core.Api return Page(resources, pagination.Limit); } - /// - /// Get genres of this show - /// - /// - /// List the genres that represent this show. - /// - /// The ID or slug of the . - /// A key to sort genres by. - /// An optional list of filters. - /// The number of genres to return. - /// A page of genres. - /// The filters or the sort parameters are invalid. - /// No show with the given ID or slug could be found. - [HttpGet("{identifier:id}/genres")] - [HttpGet("{identifier:id}/genre", Order = AlternativeRoute)] - [PartialPermission(Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetGenres(Identifier identifier, - [FromQuery] string sortBy, - [FromQuery] Dictionary where, - [FromQuery] Pagination pagination) - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), - Sort.From(sortBy), - pagination - ); - - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, pagination.Limit); - } - /// /// Get studio that made the show /// @@ -238,42 +183,6 @@ namespace Kyoo.Core.Api return await _libraryManager.Get(identifier.IsContainedIn(x => x.Shows)); } - /// - /// Get libraries containing this show - /// - /// - /// List the libraries that contain this show. If this show is contained in a collection that is contained in - /// a library, this library will be returned too. - /// - /// The ID or slug of the . - /// A key to sort libraries by. - /// An optional list of filters. - /// The number of libraries to return. - /// A page of libraries. - /// The filters or the sort parameters are invalid. - /// No show with the given ID or slug could be found. - [HttpGet("{identifier:id}/libraries")] - [HttpGet("{identifier:id}/library", Order = AlternativeRoute)] - [PartialPermission(Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetLibraries(Identifier identifier, - [FromQuery] string sortBy, - [FromQuery] Dictionary where, - [FromQuery] Pagination pagination) - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), - Sort.From(sortBy), - pagination - ); - - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, pagination.Limit); - } - /// /// Get collections containing this show /// diff --git a/back/src/Kyoo.Core/Views/Watch/WatchApi.cs b/back/src/Kyoo.Core/Views/Watch/WatchApi.cs deleted file mode 100644 index 24f53174..00000000 --- a/back/src/Kyoo.Core/Views/Watch/WatchApi.cs +++ /dev/null @@ -1,91 +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.Net.Http; -using System.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Abstractions.Models.Permissions; -using Kyoo.Abstractions.Models.Utils; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using static Kyoo.Abstractions.Models.Utils.Constants; - -namespace Kyoo.Core.Api -{ - /// - /// Retrieve information of an as a . - /// A watch item is another representation of an episode in a form easier to read and display for playback. - /// It contains streams (video, audio, subtitles) information, chapters, next and previous episodes and a bit of - /// information of the show. - /// - [Route("watch")] - [Route("watchitem", Order = AlternativeRoute)] - [ApiController] - [ApiDefinition("Watch Items", Group = WatchGroup)] - public class WatchApi : ControllerBase - { - /// - /// The library manager used to modify or retrieve information in the data store. - /// - private readonly ILibraryManager _libraryManager; - - /// - /// The http client to reach transcoder. - /// - private readonly HttpClient _client; - - /// - /// Create a new . - /// - /// - /// The library manager used to modify or retrieve information in the data store. - /// - /// The http client to reach transcoder. - public WatchApi(ILibraryManager libraryManager, HttpClient client) - { - _libraryManager = libraryManager; - _client = client; - } - - /// - /// Get a watch item - /// - /// - /// Retrieve a watch item of an episode. - /// - /// The ID or slug of the . - /// A page of items. - /// No episode with the given ID or slug could be found. - [HttpGet("{identifier:id}")] - [Permission("watch", Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetWatchItem(Identifier identifier) - { - Episode item = await identifier.Match( - id => _libraryManager.GetOrDefault(id), - slug => _libraryManager.GetOrDefault(slug) - ); - if (item == null) - return NotFound(); - return await WatchItem.FromEpisode(item, _libraryManager, _client); - } - } -} diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index b5a15d25..bef2dd16 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -41,16 +41,16 @@ namespace Kyoo.Postgresql /// public abstract class DatabaseContext : DbContext { - /// - /// All libraries of Kyoo. See . - /// - public DbSet Libraries { get; set; } - /// /// All collections of Kyoo. See . /// public DbSet Collections { get; set; } + /// + /// All movies of Kyoo. See . + /// + public DbSet Movies { get; set; } + /// /// All shows of Kyoo. See . /// @@ -66,11 +66,6 @@ namespace Kyoo.Postgresql /// public DbSet Episodes { get; set; } - /// - /// All genres of Kyoo. See . - /// - public DbSet Genres { get; set; } - /// /// All people of Kyoo. See . /// @@ -91,18 +86,38 @@ namespace Kyoo.Postgresql /// public DbSet PeopleRoles { get; set; } - /// - /// Episodes with a watch percentage. See . - /// - public DbSet WatchedEpisodes { get; set; } - /// /// The list of library items (shows and collections that are part of a library - or the global one). /// /// /// This set is ready only, on most database this will be a view. /// - public DbSet LibraryItems { get; set; } + public IQueryable LibraryItems => + Shows.Select(x => new BagItem + { + ID = x.ID, + Slug = x.Slug, + Name = x.Name, + AirDate = x.StartAir, + Poster = x.Poster, + Rest = x + }).Union(Movies.Select(x => new BagItem + { + ID = x.ID, + Slug = x.Slug, + Name = x.Name, + AirDate = x.AirDate, + Poster = x.Poster, + Rest = x + })).Union(Collections.Select(x => new BagItem + { + ID = x.ID, + Slug = x.Slug, + Name = x.Name, + AirDate = null, + Poster = x.Poster, + Rest = x + })); /// /// Add a many to many link between two resources. @@ -138,14 +153,6 @@ namespace Kyoo.Postgresql : base(options) { } - /// - /// Get the name of the metadata table of the given type. - /// - /// The type related to the metadata - /// The name of the table containing the metadata. - protected abstract string MetadataName() - where T : IMetadata; - /// /// Get the name of the link table of the two given types. /// @@ -265,15 +272,16 @@ namespace Kyoo.Postgresql .WithOne(x => x.Season) .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .HasOne(x => x.Studio) + .WithMany(x => x.Movies) + .OnDelete(DeleteBehavior.SetNull); modelBuilder.Entity() .HasOne(x => x.Studio) .WithMany(x => x.Shows) .OnDelete(DeleteBehavior.SetNull); - _HasManyToMany(modelBuilder, x => x.Collections, x => x.Libraries); - _HasManyToMany(modelBuilder, x => x.Shows, x => x.Libraries); _HasManyToMany(modelBuilder, x => x.Shows, x => x.Collections); - _HasManyToMany(modelBuilder, x => x.Genres, x => x.Shows); modelBuilder.Entity() .HasMany(x => x.Watched) @@ -281,14 +289,15 @@ namespace Kyoo.Postgresql .UsingEntity(x => x.ToTable(LinkName())); _HasMetadata(modelBuilder); + _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); - _HasImages(modelBuilder); _HasImages(modelBuilder); + _HasImages(modelBuilder); _HasImages(modelBuilder); _HasImages(modelBuilder); _HasImages(modelBuilder); @@ -299,28 +308,15 @@ namespace Kyoo.Postgresql modelBuilder.Entity() .HasKey(x => new { User = x.UserID, Episode = x.EpisodeID }); - 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(); - modelBuilder.Entity().Property(x => x.Slug).IsRequired(); - modelBuilder.Entity().Property(x => x.Slug).IsRequired(); - modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); - modelBuilder.Entity() - .HasIndex(x => x.Slug) - .IsUnique(); - modelBuilder.Entity() - .HasIndex(x => x.Slug) - .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); + modelBuilder.Entity() + .HasIndex(x => x.Slug) + .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); @@ -342,9 +338,6 @@ namespace Kyoo.Postgresql modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); - - modelBuilder.Entity() - .ToView("library_items"); } /// diff --git a/back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj b/back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj index 2eb64ec0..44ff1a9d 100644 --- a/back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj +++ b/back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/back/src/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs b/back/src/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs deleted file mode 100644 index 5321a0a1..00000000 --- a/back/src/Kyoo.Postgresql/Migrations/20210801171613_Initial.cs +++ /dev/null @@ -1,872 +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 Kyoo.Abstractions.Models; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Postgresql.Migrations -{ - /// - /// The initial migration that build most of the database. - /// - [DbContext(typeof(PostgresContext))] - [Migration("20210801171613_Initial")] - public partial class Initial : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterDatabase() - .Annotation("Npgsql:Enum:item_type", "show,movie,collection") - .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned") - .Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,attachment"); - - migrationBuilder.CreateTable( - name: "collections", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true), - images = table.Column>(type: "jsonb", nullable: true), - overview = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_collections", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "genres", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_genres", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "libraries", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true), - paths = table.Column(type: "text[]", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_libraries", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "people", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true), - images = table.Column>(type: "jsonb", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_people", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "providers", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true), - images = table.Column>(type: "jsonb", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_providers", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "studios", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_studios", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "users", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - username = table.Column(type: "text", nullable: true), - email = table.Column(type: "text", nullable: true), - password = table.Column(type: "text", nullable: true), - permissions = table.Column(type: "text[]", nullable: true), - extra_data = table.Column>(type: "jsonb", nullable: true), - images = table.Column>(type: "jsonb", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_users", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "link_library_collection", - columns: table => new - { - collection_id = table.Column(type: "integer", nullable: false), - library_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_library_collection", x => new { x.collection_id, x.library_id }); - table.ForeignKey( - name: "fk_link_library_collection_collections_collection_id", - column: x => x.collection_id, - principalTable: "collections", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_library_collection_libraries_library_id", - column: x => x.library_id, - principalTable: "libraries", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "collection_metadata_id", - columns: table => new - { - resource_id = table.Column(type: "integer", nullable: false), - provider_id = table.Column(type: "integer", nullable: false), - data_id = table.Column(type: "text", nullable: true), - link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_collection_metadata_id", x => new { x.resource_id, x.provider_id }); - table.ForeignKey( - name: "fk_collection_metadata_id_collections_collection_id", - column: x => x.resource_id, - principalTable: "collections", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_collection_metadata_id_providers_provider_id", - column: x => x.provider_id, - principalTable: "providers", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_library_provider", - columns: table => new - { - 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: "shows", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: false), - title = table.Column(type: "text", nullable: true), - aliases = table.Column(type: "text[]", nullable: true), - path = table.Column(type: "text", nullable: true), - overview = table.Column(type: "text", nullable: true), - status = table.Column(type: "status", nullable: false), - start_air = table.Column(type: "timestamp without time zone", nullable: true), - end_air = table.Column(type: "timestamp without time zone", nullable: true), - images = table.Column>(type: "jsonb", nullable: true), - is_movie = table.Column(type: "boolean", nullable: false), - studio_id = table.Column(type: "integer", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_shows", x => x.id); - table.ForeignKey( - name: "fk_shows_studios_studio_id", - column: x => x.studio_id, - principalTable: "studios", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); - }); - - 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.CreateTable( - name: "link_collection_show", - columns: table => new - { - collection_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id }); - table.ForeignKey( - name: "fk_link_collection_show_collections_collection_id", - column: x => x.collection_id, - principalTable: "collections", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_collection_show_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_library_show", - columns: table => new - { - library_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_library_show", x => new { x.library_id, x.show_id }); - table.ForeignKey( - name: "fk_link_library_show_libraries_library_id", - column: x => x.library_id, - principalTable: "libraries", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_library_show_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_show_genre", - columns: table => new - { - genre_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_show_genre", x => new { x.genre_id, x.show_id }); - table.ForeignKey( - name: "fk_link_show_genre_genres_genre_id", - column: x => x.genre_id, - principalTable: "genres", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_show_genre_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_user_show", - columns: table => new - { - users_id = table.Column(type: "integer", nullable: false), - watched_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id }); - table.ForeignKey( - name: "fk_link_user_show_shows_watched_id", - column: x => x.watched_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_user_show_users_users_id", - column: x => x.users_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "people_roles", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - people_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: false), - type = table.Column(type: "text", nullable: true), - role = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_people_roles", x => x.id); - table.ForeignKey( - name: "fk_people_roles_people_people_id", - column: x => x.people_id, - principalTable: "people", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_people_roles_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "seasons", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: true), - show_id = table.Column(type: "integer", nullable: false), - season_number = table.Column(type: "integer", nullable: false), - title = table.Column(type: "text", nullable: true), - overview = table.Column(type: "text", nullable: true), - start_date = table.Column(type: "timestamp without time zone", nullable: true), - end_date = table.Column(type: "timestamp without time zone", nullable: true), - images = table.Column>(type: "jsonb", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_seasons", x => x.id); - table.ForeignKey( - name: "fk_seasons_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - 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: "episodes", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: true), - show_id = table.Column(type: "integer", nullable: false), - season_id = table.Column(type: "integer", nullable: true), - season_number = table.Column(type: "integer", nullable: true), - episode_number = table.Column(type: "integer", nullable: true), - absolute_number = table.Column(type: "integer", nullable: true), - path = table.Column(type: "text", nullable: true), - images = table.Column>(type: "jsonb", nullable: true), - title = table.Column(type: "text", nullable: true), - overview = table.Column(type: "text", nullable: true), - release_date = table.Column(type: "timestamp without time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_episodes", x => x.id); - table.ForeignKey( - name: "fk_episodes_seasons_season_id", - column: x => x.season_id, - principalTable: "seasons", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_episodes_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - 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: "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: "tracks", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "text", nullable: true), - title = table.Column(type: "text", nullable: true), - language = table.Column(type: "text", nullable: true), - codec = table.Column(type: "text", nullable: true), - is_default = table.Column(type: "boolean", nullable: false), - is_forced = table.Column(type: "boolean", nullable: false), - is_external = table.Column(type: "boolean", nullable: false), - path = table.Column(type: "text", nullable: true), - type = table.Column(type: "stream_type", nullable: false), - episode_id = table.Column(type: "integer", nullable: false), - track_index = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_tracks", x => x.id); - table.ForeignKey( - name: "fk_tracks_episodes_episode_id", - column: x => x.episode_id, - principalTable: "episodes", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "watched_episodes", - columns: table => new - { - user_id = table.Column(type: "integer", nullable: false), - episode_id = table.Column(type: "integer", nullable: false), - watched_percentage = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_watched_episodes", x => new { x.user_id, x.episode_id }); - table.ForeignKey( - name: "fk_watched_episodes_episodes_episode_id", - column: x => x.episode_id, - principalTable: "episodes", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_watched_episodes_users_user_id", - column: x => x.user_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_collection_metadata_id_provider_id", - table: "collection_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_collections_slug", - table: "collections", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_episode_metadata_id_provider_id", - table: "episode_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_episodes_season_id", - table: "episodes", - column: "season_id"); - - migrationBuilder.CreateIndex( - name: "ix_episodes_show_id_season_number_episode_number_absolute_numb", - table: "episodes", - columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_episodes_slug", - table: "episodes", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_genres_slug", - table: "genres", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_libraries_slug", - table: "libraries", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_link_collection_show_show_id", - table: "link_collection_show", - column: "show_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_library_collection_library_id", - table: "link_library_collection", - column: "library_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_library_provider_provider_id", - table: "link_library_provider", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_library_show_show_id", - table: "link_library_show", - column: "show_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_show_genre_show_id", - table: "link_show_genre", - column: "show_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_user_show_watched_id", - table: "link_user_show", - column: "watched_id"); - - migrationBuilder.CreateIndex( - name: "ix_people_slug", - table: "people", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_people_metadata_id_provider_id", - table: "people_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_people_roles_people_id", - table: "people_roles", - column: "people_id"); - - migrationBuilder.CreateIndex( - name: "ix_people_roles_show_id", - table: "people_roles", - column: "show_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_seasons_show_id_season_number", - table: "seasons", - columns: new[] { "show_id", "season_number" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_seasons_slug", - table: "seasons", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_show_metadata_id_provider_id", - table: "show_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_shows_slug", - table: "shows", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_shows_studio_id", - table: "shows", - column: "studio_id"); - - migrationBuilder.CreateIndex( - name: "ix_studio_metadata_id_provider_id", - table: "studio_metadata_id", - column: "provider_id"); - - migrationBuilder.CreateIndex( - name: "ix_studios_slug", - table: "studios", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_tracks_episode_id_type_language_track_index_is_forced", - table: "tracks", - columns: new[] { "episode_id", "type", "language", "track_index", "is_forced" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_tracks_slug", - table: "tracks", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_users_slug", - table: "users", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_watched_episodes_episode_id", - table: "watched_episodes", - column: "episode_id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "collection_metadata_id"); - - migrationBuilder.DropTable( - name: "episode_metadata_id"); - - migrationBuilder.DropTable( - name: "link_collection_show"); - - migrationBuilder.DropTable( - name: "link_library_collection"); - - migrationBuilder.DropTable( - name: "link_library_provider"); - - migrationBuilder.DropTable( - name: "link_library_show"); - - migrationBuilder.DropTable( - name: "link_show_genre"); - - migrationBuilder.DropTable( - name: "link_user_show"); - - migrationBuilder.DropTable( - name: "people_metadata_id"); - - migrationBuilder.DropTable( - name: "people_roles"); - - migrationBuilder.DropTable( - name: "season_metadata_id"); - - migrationBuilder.DropTable( - name: "show_metadata_id"); - - migrationBuilder.DropTable( - name: "studio_metadata_id"); - - migrationBuilder.DropTable( - name: "tracks"); - - migrationBuilder.DropTable( - name: "watched_episodes"); - - migrationBuilder.DropTable( - name: "collections"); - - migrationBuilder.DropTable( - name: "libraries"); - - migrationBuilder.DropTable( - name: "genres"); - - migrationBuilder.DropTable( - name: "people"); - - migrationBuilder.DropTable( - name: "providers"); - - migrationBuilder.DropTable( - name: "episodes"); - - migrationBuilder.DropTable( - name: "users"); - - migrationBuilder.DropTable( - name: "seasons"); - - migrationBuilder.DropTable( - name: "shows"); - - migrationBuilder.DropTable( - name: "studios"); - } - } -} diff --git a/back/src/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs b/back/src/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs deleted file mode 100644 index cc0468a5..00000000 --- a/back/src/Kyoo.Postgresql/Migrations/20210801171641_Triggers.cs +++ /dev/null @@ -1,192 +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 Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Kyoo.Postgresql.Migrations -{ - /// - /// A migration that adds postgres triggers to update slugs. - /// - [DbContext(typeof(PostgresContext))] - [Migration("20210801171641_Triggers")] - public partial class Triggers : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE FUNCTION season_slug_update() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - NEW.slug := CONCAT( - (SELECT slug FROM shows WHERE id = NEW.show_id), - '-s', - NEW.season_number - ); - RETURN NEW; - END - $$;"); - - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE TRIGGER season_slug_trigger BEFORE INSERT OR UPDATE OF season_number, show_id ON seasons - FOR EACH ROW EXECUTE PROCEDURE season_slug_update();"); - - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE FUNCTION episode_slug_update() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - NEW.slug := CONCAT( - (SELECT slug FROM shows WHERE id = NEW.show_id), - CASE - WHEN NEW.season_number IS NULL AND NEW.episode_number IS NULL THEN NULL - WHEN NEW.season_number IS NULL THEN CONCAT('-', NEW.absolute_number) - ELSE CONCAT('-s', NEW.season_number, 'e', NEW.episode_number) - END - ); - RETURN NEW; - END - $$;"); - - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE TRIGGER episode_slug_trigger - BEFORE INSERT OR UPDATE OF absolute_number, episode_number, season_number, show_id ON episodes - FOR EACH ROW EXECUTE PROCEDURE episode_slug_update();"); - - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE FUNCTION show_slug_update() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - UPDATE seasons SET slug = CONCAT(NEW.slug, '-s', season_number) WHERE show_id = NEW.id; - UPDATE episodes SET slug = CASE - WHEN season_number IS NULL AND episode_number IS NULL THEN NEW.slug - WHEN season_number IS NULL THEN CONCAT(NEW.slug, '-', absolute_number) - ELSE CONCAT(NEW.slug, '-s', season_number, 'e', episode_number) - END WHERE show_id = NEW.id; - RETURN NEW; - END - $$;"); - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE TRIGGER show_slug_trigger AFTER UPDATE OF slug ON shows - FOR EACH ROW EXECUTE PROCEDURE show_slug_update();"); - - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE FUNCTION episode_update_tracks_slug() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - UPDATE tracks SET slug = CONCAT( - NEW.slug, - '.', language, - CASE (track_index) - WHEN 0 THEN '' - ELSE CONCAT('-', track_index) - END, - CASE (is_forced) - WHEN false THEN '' - ELSE '.forced' - END, - '.', type - ) WHERE episode_id = NEW.id; - RETURN NEW; - END; - $$;"); - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE TRIGGER episode_track_slug_trigger AFTER UPDATE OF slug ON episodes - FOR EACH ROW EXECUTE PROCEDURE episode_update_tracks_slug();"); - - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE FUNCTION track_slug_update() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - IF NEW.track_index = 0 THEN - NEW.track_index := (SELECT COUNT(*) FROM tracks - WHERE episode_id = NEW.episode_id AND type = NEW.type - AND language = NEW.language AND is_forced = NEW.is_forced); - END IF; - NEW.slug := CONCAT( - (SELECT slug FROM episodes WHERE id = NEW.episode_id), - '.', COALESCE(NEW.language, 'und'), - CASE (NEW.track_index) - WHEN 0 THEN '' - ELSE CONCAT('-', NEW.track_index) - END, - CASE (NEW.is_forced) - WHEN false THEN '' - ELSE '.forced' - END, - '.', NEW.type - ); - RETURN NEW; - END - $$;"); - // language=PostgreSQL - migrationBuilder.Sql(@" - CREATE TRIGGER track_slug_trigger - BEFORE INSERT OR UPDATE OF episode_id, is_forced, language, track_index, type ON tracks - FOR EACH ROW EXECUTE PROCEDURE track_slug_update();"); - - MigrationHelper.CreateLibraryItemsView(migrationBuilder); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - // language=PostgreSQL - migrationBuilder.Sql("DROP TRIGGER show_slug_trigger ON shows;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION show_slug_update;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP TRIGGER season_slug_trigger ON seasons;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION season_slug_update;"); - // language=PostgreSQL - migrationBuilder.Sql("DROP TRIGGER episode_slug_trigger ON episodes;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION episode_slug_update;"); - // language=PostgreSQL - migrationBuilder.Sql("DROP TRIGGER track_slug_trigger ON tracks;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION track_slug_update;"); - // language=PostgreSQL - migrationBuilder.Sql("DROP TRIGGER episode_track_slug_trigger ON episodes;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION episode_update_tracks_slug;"); - MigrationHelper.DropLibraryItemsView(migrationBuilder); - } - } -} diff --git a/back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.cs b/back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.cs deleted file mode 100644 index f235cdf6..00000000 --- a/back/src/Kyoo.Postgresql/Migrations/20230724144449_RemoveTriggers.cs +++ /dev/null @@ -1,114 +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 Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Kyoo.Postgresql.Migrations -{ - /// - /// Remove triggers - /// - [DbContext(typeof(PostgresContext))] - [Migration("20230724144449_RemoveTriggers")] - public partial class RemoveTriggers : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - // language=PostgreSQL - migrationBuilder.Sql("DROP TRIGGER show_slug_trigger ON shows;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION show_slug_update;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP TRIGGER season_slug_trigger ON seasons;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION season_slug_update;"); - // language=PostgreSQL - migrationBuilder.Sql("DROP TRIGGER episode_slug_trigger ON episodes;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION episode_slug_update;"); - // language=PostgreSQL - migrationBuilder.Sql("DROP TRIGGER track_slug_trigger ON tracks;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION track_slug_update;"); - // language=PostgreSQL - migrationBuilder.Sql("DROP TRIGGER episode_track_slug_trigger ON episodes;"); - // language=PostgreSQL - migrationBuilder.Sql(@"DROP FUNCTION episode_update_tracks_slug;"); - - migrationBuilder.AlterColumn( - name: "slug", - table: "tracks", - type: "text", - nullable: false, - defaultValue: string.Empty, - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "slug", - table: "seasons", - type: "text", - nullable: false, - defaultValue: string.Empty, - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "slug", - table: "episodes", - type: "text", - nullable: false, - defaultValue: string.Empty, - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "slug", - table: "tracks", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "slug", - table: "seasons", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "slug", - table: "episodes", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - } - } -} diff --git a/back/src/Kyoo.Postgresql/Migrations/20230726100747_Timestamp.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230726100747_Timestamp.Designer.cs deleted file mode 100644 index 2a80f33b..00000000 --- a/back/src/Kyoo.Postgresql/Migrations/20230726100747_Timestamp.Designer.cs +++ /dev/null @@ -1,1273 +0,0 @@ -// -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 -{ - partial class Timestamp - { - /// - 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.HasPostgresEnum(modelBuilder, "stream_type", new[] { "unknown", "video", "audio", "subtitle" }); - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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.Provider", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); - - b.HasKey("ID") - .HasName("pk_providers"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_providers_slug"); - - b.ToTable("providers", (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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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.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("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.Track", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - - b.Property("Codec") - .HasColumnType("text") - .HasColumnName("codec"); - - b.Property("EpisodeID") - .HasColumnType("integer") - .HasColumnName("episode_id"); - - b.Property("IsDefault") - .HasColumnType("boolean") - .HasColumnName("is_default"); - - b.Property("IsExternal") - .HasColumnType("boolean") - .HasColumnName("is_external"); - - b.Property("IsForced") - .HasColumnType("boolean") - .HasColumnName("is_forced"); - - b.Property("Language") - .HasColumnType("text") - .HasColumnName("language"); - - b.Property("Path") - .HasColumnType("text") - .HasColumnName("path"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); - - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); - - b.Property("TrackIndex") - .HasColumnType("integer") - .HasColumnName("track_index"); - - b.Property("Type") - .HasColumnType("stream_type") - .HasColumnName("type"); - - b.HasKey("ID") - .HasName("pk_tracks"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_tracks_slug"); - - b.HasIndex("EpisodeID", "Type", "Language", "TrackIndex", "IsForced") - .IsUnique() - .HasDatabaseName("ix_tracks_episode_id_type_language_track_index_is_forced"); - - b.ToTable("tracks", (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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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("collection_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); - - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_collection_metadata_id"); - - b.HasIndex("ProviderID") - .HasDatabaseName("ix_collection_metadata_id_provider_id"); - - b.ToTable("collection_metadata_id", (string)null); - }); - - modelBuilder.Entity("episode_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); - - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_episode_metadata_id"); - - b.HasIndex("ProviderID") - .HasDatabaseName("ix_episode_metadata_id_provider_id"); - - b.ToTable("episode_metadata_id", (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_provider", b => - { - b.Property("library_id") - .HasColumnType("integer") - .HasColumnName("library_id"); - - b.Property("provider_id") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.HasKey("library_id", "provider_id") - .HasName("pk_link_library_provider"); - - b.HasIndex("provider_id") - .HasDatabaseName("ix_link_library_provider_provider_id"); - - b.ToTable("link_library_provider", (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("people_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); - - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_people_metadata_id"); - - b.HasIndex("ProviderID") - .HasDatabaseName("ix_people_metadata_id_provider_id"); - - b.ToTable("people_metadata_id", (string)null); - }); - - modelBuilder.Entity("season_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); - - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_season_metadata_id"); - - b.HasIndex("ProviderID") - .HasDatabaseName("ix_season_metadata_id_provider_id"); - - b.ToTable("season_metadata_id", (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.Track", b => - { - b.HasOne("Kyoo.Abstractions.Models.Episode", "Episode") - .WithMany("Tracks") - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tracks_episodes_episode_id"); - - b.Navigation("Episode"); - }); - - 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"); - - b.Navigation("Tracks"); - }); - - 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"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/back/src/Kyoo.Postgresql/Migrations/20230726100747_Timestamp.cs b/back/src/Kyoo.Postgresql/Migrations/20230726100747_Timestamp.cs deleted file mode 100644 index 3fd9cd6b..00000000 --- a/back/src/Kyoo.Postgresql/Migrations/20230726100747_Timestamp.cs +++ /dev/null @@ -1,138 +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 Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Kyoo.Postgresql.Migrations -{ - /// - [DbContext(typeof(PostgresContext))] - [Migration("20230726100747_Timestamp")] - public partial class Timestamp : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - MigrationHelper.DropLibraryItemsView(migrationBuilder); - - migrationBuilder.AlterColumn( - name: "start_air", - table: "shows", - type: "timestamp with time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "end_air", - table: "shows", - type: "timestamp with time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "start_date", - table: "seasons", - type: "timestamp with time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "end_date", - table: "seasons", - type: "timestamp with time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "release_date", - table: "episodes", - type: "timestamp with time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone", - oldNullable: true); - - MigrationHelper.CreateLibraryItemsView(migrationBuilder); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - MigrationHelper.DropLibraryItemsView(migrationBuilder); - - migrationBuilder.AlterColumn( - name: "start_air", - table: "shows", - type: "timestamp without time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "end_air", - table: "shows", - type: "timestamp without time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "start_date", - table: "seasons", - type: "timestamp without time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "end_date", - table: "seasons", - type: "timestamp without time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "release_date", - table: "episodes", - type: "timestamp without time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone", - oldNullable: true); - - MigrationHelper.CreateLibraryItemsView(migrationBuilder); - } - } -} diff --git a/back/src/Kyoo.Postgresql/Migrations/20230731065523_RemoveTracks.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230731065523_RemoveTracks.Designer.cs deleted file mode 100644 index e86a0b3f..00000000 --- a/back/src/Kyoo.Postgresql/Migrations/20230731065523_RemoveTracks.Designer.cs +++ /dev/null @@ -1,1189 +0,0 @@ -// -using System; -using System.Collections.Generic; -using Kyoo.Abstractions.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Kyoo.Postgresql.Migrations -{ - [DbContext(typeof(PostgresContext))] - [Migration("20230731065523_RemoveTracks")] - partial class RemoveTracks - { - /// - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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.Provider", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - - b.Property>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text") - .HasColumnName("slug"); - - b.HasKey("ID") - .HasName("pk_providers"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_providers_slug"); - - b.ToTable("providers", (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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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.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("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>("Images") - .HasColumnType("jsonb") - .HasColumnName("images"); - - 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("collection_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); - - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_collection_metadata_id"); - - b.HasIndex("ProviderID") - .HasDatabaseName("ix_collection_metadata_id_provider_id"); - - b.ToTable("collection_metadata_id", (string)null); - }); - - modelBuilder.Entity("episode_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); - - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_episode_metadata_id"); - - b.HasIndex("ProviderID") - .HasDatabaseName("ix_episode_metadata_id_provider_id"); - - b.ToTable("episode_metadata_id", (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_provider", b => - { - b.Property("library_id") - .HasColumnType("integer") - .HasColumnName("library_id"); - - b.Property("provider_id") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.HasKey("library_id", "provider_id") - .HasName("pk_link_library_provider"); - - b.HasIndex("provider_id") - .HasDatabaseName("ix_link_library_provider_provider_id"); - - b.ToTable("link_library_provider", (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("people_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); - - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_people_metadata_id"); - - b.HasIndex("ProviderID") - .HasDatabaseName("ix_people_metadata_id_provider_id"); - - b.ToTable("people_metadata_id", (string)null); - }); - - modelBuilder.Entity("season_metadata_id", b => - { - b.Property("ResourceID") - .HasColumnType("integer") - .HasColumnName("resource_id"); - - b.Property("ProviderID") - .HasColumnType("integer") - .HasColumnName("provider_id"); - - b.Property("DataID") - .HasColumnType("text") - .HasColumnName("data_id"); - - b.Property("Link") - .HasColumnType("text") - .HasColumnName("link"); - - b.HasKey("ResourceID", "ProviderID") - .HasName("pk_season_metadata_id"); - - b.HasIndex("ProviderID") - .HasDatabaseName("ix_season_metadata_id_provider_id"); - - b.ToTable("season_metadata_id", (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"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/back/src/Kyoo.Postgresql/Migrations/20230731065523_RemoveTracks.cs b/back/src/Kyoo.Postgresql/Migrations/20230731065523_RemoveTracks.cs deleted file mode 100644 index a9e58f16..00000000 --- a/back/src/Kyoo.Postgresql/Migrations/20230731065523_RemoveTracks.cs +++ /dev/null @@ -1,95 +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 Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Kyoo.Postgresql.Migrations -{ - /// - public partial class RemoveTracks : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "tracks"); - - migrationBuilder.AlterDatabase() - .Annotation("Npgsql:Enum:item_type", "show,movie,collection") - .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned") - .OldAnnotation("Npgsql:Enum:item_type", "show,movie,collection") - .OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned") - .OldAnnotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterDatabase() - .Annotation("Npgsql:Enum:item_type", "show,movie,collection") - .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned") - .Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle") - .OldAnnotation("Npgsql:Enum:item_type", "show,movie,collection") - .OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned"); - - migrationBuilder.CreateTable( - name: "tracks", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - episode_id = table.Column(type: "integer", nullable: false), - codec = table.Column(type: "text", nullable: true), - is_default = table.Column(type: "boolean", nullable: false), - is_external = table.Column(type: "boolean", nullable: false), - is_forced = table.Column(type: "boolean", nullable: false), - language = table.Column(type: "text", nullable: true), - path = table.Column(type: "text", nullable: true), - slug = table.Column(type: "text", nullable: false), - title = table.Column(type: "text", nullable: true), - track_index = table.Column(type: "integer", nullable: false), - type = table.Column(type: "stream_type", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_tracks", x => x.id); - table.ForeignKey( - name: "fk_tracks_episodes_episode_id", - column: x => x.episode_id, - principalTable: "episodes", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_tracks_episode_id_type_language_track_index_is_forced", - table: "tracks", - columns: new[] { "episode_id", "type", "language", "track_index", "is_forced" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_tracks_slug", - table: "tracks", - column: "slug", - unique: true); - } - } -} diff --git a/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.cs b/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.cs deleted file mode 100644 index 98a3a9af..00000000 --- a/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.cs +++ /dev/null @@ -1,772 +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 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/20230804143919_AddBlurhash.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.Designer.cs similarity index 80% rename from back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.Designer.cs rename to back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.Designer.cs index cc630b9f..98114327 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230804143919_AddBlurhash.Designer.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.Designer.cs @@ -1,6 +1,5 @@ // using System; -using System.Collections.Generic; using Kyoo.Abstractions.Models; using Kyoo.Postgresql; using Microsoft.EntityFrameworkCore; @@ -14,8 +13,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20230804143919_AddBlurhash")] - partial class AddBlurhash + [Migration("20230805051120_initial")] + partial class initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -25,10 +24,29 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("ProductVersion", "7.0.9") .HasAnnotation("Relational:MaxIdentifierLength", 63); - NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_type", new[] { "show", "movie", "collection" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "action", "adventure", "animation", "comedy", "crime", "documentary", "drama", "family", "fantasy", "history", "horror", "music", "mystery", "romance", "science_fiction", "thriller", "war", "western" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("CollectionMovie", b => + { + b.Property("CollectionsID") + .HasColumnType("integer") + .HasColumnName("collections_id"); + + b.Property("MoviesID") + .HasColumnType("integer") + .HasColumnName("movies_id"); + + b.HasKey("CollectionsID", "MoviesID") + .HasName("pk_collection_movie"); + + b.HasIndex("MoviesID") + .HasDatabaseName("ix_collection_movie_movies_id"); + + b.ToTable("collection_movie", (string)null); + }); + modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => { b.Property("ID") @@ -38,11 +56,13 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); b.Property("Name") + .IsRequired() .HasColumnType("text") .HasColumnName("name"); @@ -52,7 +72,8 @@ namespace Kyoo.Postgresql.Migrations b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.HasKey("ID") @@ -82,15 +103,21 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("episode_number"); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); b.Property("Path") + .IsRequired() .HasColumnType("text") .HasColumnName("path"); @@ -112,13 +139,10 @@ namespace Kyoo.Postgresql.Migrations b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); - b.HasKey("ID") .HasName("pk_episodes"); @@ -136,7 +160,7 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("episodes", (string)null); }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Genre", b => + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -145,97 +169,77 @@ namespace Kyoo.Postgresql.Migrations 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") + b.Property("AirDate") .HasColumnType("timestamp with time zone") - .HasColumnName("end_air"); + .HasColumnName("air_date"); + + b.Property("Aliases") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Slug") + b.Property("Path") + .IsRequired() .HasColumnType("text") + .HasColumnName("path"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.Property("StartAir") - .HasColumnType("timestamp with time zone") - .HasColumnName("start_air"); - - b.Property("Status") + b.Property("Status") .HasColumnType("status") .HasColumnName("status"); - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); + b.Property("StudioID") + .HasColumnType("integer") + .HasColumnName("studio_id"); - b.Property("Type") - .HasColumnType("item_type") - .HasColumnName("type"); + b.Property("Tagline") + .HasColumnType("text") + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); + + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); b.HasKey("ID") - .HasName("pk_library_items"); + .HasName("pk_movies"); - b.ToTable((string)null); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_movies_slug"); - b.ToView("library_items", (string)null); + b.HasIndex("StudioID") + .HasDatabaseName("ix_movies_studio_id"); + + b.ToTable("movies", (string)null); }); modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => @@ -247,17 +251,20 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); b.Property("Name") + .IsRequired() .HasColumnType("text") .HasColumnName("name"); b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.HasKey("ID") @@ -279,25 +286,34 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + b.Property("MovieID") + .HasColumnType("integer") + .HasColumnName("movie_id"); + b.Property("PeopleID") .HasColumnType("integer") .HasColumnName("people_id"); b.Property("Role") + .IsRequired() .HasColumnType("text") .HasColumnName("role"); - b.Property("ShowID") + b.Property("ShowID") .HasColumnType("integer") .HasColumnName("show_id"); b.Property("Type") + .IsRequired() .HasColumnType("text") .HasColumnName("type"); b.HasKey("ID") .HasName("pk_people_roles"); + b.HasIndex("MovieID") + .HasDatabaseName("ix_people_roles_movie_id"); + b.HasIndex("PeopleID") .HasDatabaseName("ix_people_roles_people_id"); @@ -320,9 +336,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("end_date"); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); b.Property("Overview") .HasColumnType("text") @@ -338,17 +359,14 @@ namespace Kyoo.Postgresql.Migrations b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .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"); @@ -373,6 +391,7 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); b.Property("Aliases") + .IsRequired() .HasColumnType("text[]") .HasColumnName("aliases"); @@ -380,25 +399,29 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("end_air"); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); - b.Property("IsMovie") - .HasColumnType("boolean") - .HasColumnName("is_movie"); + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Path") - .HasColumnType("text") - .HasColumnName("path"); - b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.Property("StartAir") @@ -413,9 +436,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("studio_id"); - b.Property("Title") + b.Property("Tagline") .HasColumnType("text") - .HasColumnName("title"); + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); b.Property("Trailer") .HasColumnType("text") @@ -443,17 +471,20 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); b.Property("Name") + .IsRequired() .HasColumnType("text") .HasColumnName("name"); b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.HasKey("ID") @@ -476,27 +507,28 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); b.Property("Email") + .IsRequired() .HasColumnType("text") .HasColumnName("email"); - b.Property>("ExtraData") - .HasColumnType("jsonb") - .HasColumnName("extra_data"); - b.Property("Password") + .IsRequired() .HasColumnType("text") .HasColumnName("password"); b.Property("Permissions") + .IsRequired() .HasColumnType("text[]") .HasColumnName("permissions"); b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.Property("Username") + .IsRequired() .HasColumnType("text") .HasColumnName("username"); @@ -525,12 +557,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("watched_percentage"); b.HasKey("UserID", "EpisodeID") - .HasName("pk_watched_episodes"); + .HasName("pk_watched_episode"); b.HasIndex("EpisodeID") - .HasDatabaseName("ix_watched_episodes_episode_id"); + .HasDatabaseName("ix_watched_episode_episode_id"); - b.ToTable("watched_episodes", (string)null); + b.ToTable("watched_episode", (string)null); }); modelBuilder.Entity("ShowUser", b => @@ -571,61 +603,21 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("link_collection_show", (string)null); }); - modelBuilder.Entity("link_library_collection", b => + modelBuilder.Entity("CollectionMovie", b => { - b.Property("collection_id") - .HasColumnType("integer") - .HasColumnName("collection_id"); + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_collection_movie_collections_collections_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); + b.HasOne("Kyoo.Abstractions.Models.Movie", null) + .WithMany() + .HasForeignKey("MoviesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_collection_movie_movies_movies_id"); }); modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => @@ -637,11 +629,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -661,11 +655,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -685,11 +681,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -731,11 +729,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -755,11 +755,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -779,11 +781,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -807,87 +811,98 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Thumbnail"); }); - modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { + b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") + .WithMany("Movies") + .HasForeignKey("StudioID") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_movies_studios_studio_id"); + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("LibraryItemID") + b1.Property("MovieID") .HasColumnType("integer") - .HasColumnName("library_item_id"); + .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") - .HasColumnName("blurhash"); + .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") - .HasColumnName("source"); + .HasColumnName("logo_source"); - b1.HasKey("LibraryItemID"); + b1.HasKey("MovieID"); - b1.ToTable((string)null); - - b1.ToView("library_items"); + b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("LibraryItemID"); + .HasForeignKey("MovieID") + .HasConstraintName("fk_movies_movies_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("LibraryItemID") + b1.Property("MovieID") .HasColumnType("integer") - .HasColumnName("library_item_id"); + .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") - .HasColumnName("blurhash"); + .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") - .HasColumnName("source"); + .HasColumnName("poster_source"); - b1.HasKey("LibraryItemID"); + b1.HasKey("MovieID"); - b1.ToTable((string)null); - - b1.ToView("library_items"); + b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("LibraryItemID"); + .HasForeignKey("MovieID") + .HasConstraintName("fk_movies_movies_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("LibraryItemID") + b1.Property("MovieID") .HasColumnType("integer") - .HasColumnName("library_item_id"); + .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") - .HasColumnName("blurhash"); + .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") - .HasColumnName("source"); + .HasColumnName("thumbnail_source"); - b1.HasKey("LibraryItemID"); + b1.HasKey("MovieID"); - b1.ToTable((string)null); - - b1.ToView("library_items"); + b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("LibraryItemID"); + .HasForeignKey("MovieID") + .HasConstraintName("fk_movies_movies_id"); }); b.Navigation("Logo"); b.Navigation("Poster"); + b.Navigation("Studio"); + b.Navigation("Thumbnail"); }); @@ -900,11 +915,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -924,11 +941,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -948,11 +967,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -974,6 +995,11 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => { + b.HasOne("Kyoo.Abstractions.Models.Movie", "Movie") + .WithMany("People") + .HasForeignKey("MovieID") + .HasConstraintName("fk_people_roles_movies_movie_id"); + b.HasOne("Kyoo.Abstractions.Models.People", "People") .WithMany("Roles") .HasForeignKey("PeopleID") @@ -984,10 +1010,10 @@ namespace Kyoo.Postgresql.Migrations b.HasOne("Kyoo.Abstractions.Models.Show", "Show") .WithMany("People") .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() .HasConstraintName("fk_people_roles_shows_show_id"); + b.Navigation("Movie"); + b.Navigation("People"); b.Navigation("Show"); @@ -1009,11 +1035,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -1033,11 +1061,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -1057,11 +1087,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -1098,11 +1130,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -1122,11 +1156,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -1146,11 +1182,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -1181,11 +1219,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -1208,14 +1248,14 @@ namespace Kyoo.Postgresql.Migrations .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_watched_episodes_episodes_episode_id"); + .HasConstraintName("fk_watched_episode_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"); + .HasConstraintName("fk_watched_episode_users_user_id"); b.Navigation("Episode"); }); @@ -1254,55 +1294,9 @@ namespace Kyoo.Postgresql.Migrations .HasConstraintName("fk_link_collection_show_shows_show_id"); }); - modelBuilder.Entity("link_library_collection", b => + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", 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"); + b.Navigation("People"); }); modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => @@ -1326,6 +1320,8 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => { + b.Navigation("Movies"); + b.Navigation("Shows"); }); diff --git a/back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.cs b/back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.cs new file mode 100644 index 00000000..40e8dd3a --- /dev/null +++ b/back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.cs @@ -0,0 +1,528 @@ +using System; +using Kyoo.Abstractions.Models; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Kyoo.Postgresql.Migrations +{ + /// + public partial class initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:Enum:genre", "action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western") + .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned"); + + migrationBuilder.CreateTable( + name: "collections", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + overview = table.Column(type: "text", nullable: true), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_collections", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "people", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_people", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "studios", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_studios", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "users", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + username = table.Column(type: "text", nullable: false), + email = table.Column(type: "text", nullable: false), + password = table.Column(type: "text", nullable: false), + permissions = table.Column(type: "text[]", nullable: false), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_users", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "movies", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + tagline = table.Column(type: "text", nullable: true), + aliases = table.Column(type: "text[]", nullable: false), + path = table.Column(type: "text", nullable: false), + overview = table.Column(type: "text", nullable: true), + tags = table.Column(type: "text[]", nullable: false), + genres = table.Column(type: "genre[]", nullable: false), + status = table.Column(type: "status", nullable: false), + air_date = table.Column(type: "timestamp with time zone", nullable: true), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + trailer = table.Column(type: "text", nullable: true), + external_id = table.Column(type: "json", nullable: false), + studio_id = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_movies", x => x.id); + table.ForeignKey( + name: "fk_movies_studios_studio_id", + column: x => x.studio_id, + principalTable: "studios", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "shows", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + tagline = table.Column(type: "text", nullable: true), + aliases = table.Column(type: "text[]", nullable: false), + overview = table.Column(type: "text", nullable: true), + tags = table.Column(type: "text[]", nullable: false), + genres = table.Column(type: "genre[]", nullable: false), + status = table.Column(type: "status", nullable: false), + start_air = table.Column(type: "timestamp with time zone", nullable: true), + end_air = table.Column(type: "timestamp with time zone", nullable: true), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + trailer = table.Column(type: "text", nullable: true), + external_id = table.Column(type: "json", nullable: false), + studio_id = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_shows", x => x.id); + table.ForeignKey( + name: "fk_shows_studios_studio_id", + column: x => x.studio_id, + principalTable: "studios", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "collection_movie", + columns: table => new + { + collections_id = table.Column(type: "integer", nullable: false), + movies_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_collection_movie", x => new { x.collections_id, x.movies_id }); + table.ForeignKey( + name: "fk_collection_movie_collections_collections_id", + column: x => x.collections_id, + principalTable: "collections", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_collection_movie_movies_movies_id", + column: x => x.movies_id, + principalTable: "movies", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_collection_show", + columns: table => new + { + collection_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id }); + table.ForeignKey( + name: "fk_link_collection_show_collections_collection_id", + column: x => x.collection_id, + principalTable: "collections", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_collection_show_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_user_show", + columns: table => new + { + users_id = table.Column(type: "integer", nullable: false), + watched_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id }); + table.ForeignKey( + name: "fk_link_user_show_shows_watched_id", + column: x => x.watched_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_user_show_users_users_id", + column: x => x.users_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "people_roles", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + people_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: true), + movie_id = table.Column(type: "integer", nullable: true), + type = table.Column(type: "text", nullable: false), + role = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_people_roles", x => x.id); + table.ForeignKey( + name: "fk_people_roles_movies_movie_id", + column: x => x.movie_id, + principalTable: "movies", + principalColumn: "id"); + table.ForeignKey( + name: "fk_people_roles_people_people_id", + column: x => x.people_id, + principalTable: "people", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_people_roles_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "seasons", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + show_id = table.Column(type: "integer", nullable: false), + season_number = table.Column(type: "integer", nullable: false), + name = table.Column(type: "text", nullable: true), + overview = table.Column(type: "text", nullable: true), + start_date = table.Column(type: "timestamp with time zone", nullable: true), + end_date = table.Column(type: "timestamp with time zone", nullable: true), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_seasons", x => x.id); + table.ForeignKey( + name: "fk_seasons_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "episodes", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + show_id = table.Column(type: "integer", nullable: false), + season_id = table.Column(type: "integer", nullable: true), + season_number = table.Column(type: "integer", nullable: true), + episode_number = table.Column(type: "integer", nullable: true), + absolute_number = table.Column(type: "integer", nullable: true), + path = table.Column(type: "text", nullable: false), + name = table.Column(type: "text", nullable: true), + overview = table.Column(type: "text", nullable: true), + release_date = table.Column(type: "timestamp with time zone", nullable: true), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_episodes", x => x.id); + table.ForeignKey( + name: "fk_episodes_seasons_season_id", + column: x => x.season_id, + principalTable: "seasons", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_episodes_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "watched_episode", + columns: table => new + { + user_id = table.Column(type: "integer", nullable: false), + episode_id = table.Column(type: "integer", nullable: false), + watched_percentage = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_watched_episode", x => new { x.user_id, x.episode_id }); + table.ForeignKey( + name: "fk_watched_episode_episodes_episode_id", + column: x => x.episode_id, + principalTable: "episodes", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_watched_episode_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_collection_movie_movies_id", + table: "collection_movie", + column: "movies_id"); + + migrationBuilder.CreateIndex( + name: "ix_collections_slug", + table: "collections", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_episodes_season_id", + table: "episodes", + column: "season_id"); + + migrationBuilder.CreateIndex( + name: "ix_episodes_show_id_season_number_episode_number_absolute_numb", + table: "episodes", + columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_episodes_slug", + table: "episodes", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_link_collection_show_show_id", + table: "link_collection_show", + column: "show_id"); + + migrationBuilder.CreateIndex( + name: "ix_link_user_show_watched_id", + table: "link_user_show", + column: "watched_id"); + + migrationBuilder.CreateIndex( + name: "ix_movies_slug", + table: "movies", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_movies_studio_id", + table: "movies", + column: "studio_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_slug", + table: "people", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_people_roles_movie_id", + table: "people_roles", + column: "movie_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_roles_people_id", + table: "people_roles", + column: "people_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_roles_show_id", + table: "people_roles", + column: "show_id"); + + migrationBuilder.CreateIndex( + name: "ix_seasons_show_id_season_number", + table: "seasons", + columns: new[] { "show_id", "season_number" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_seasons_slug", + table: "seasons", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_shows_slug", + table: "shows", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_shows_studio_id", + table: "shows", + column: "studio_id"); + + migrationBuilder.CreateIndex( + name: "ix_studios_slug", + table: "studios", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_users_slug", + table: "users", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_watched_episode_episode_id", + table: "watched_episode", + column: "episode_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "collection_movie"); + + migrationBuilder.DropTable( + name: "link_collection_show"); + + migrationBuilder.DropTable( + name: "link_user_show"); + + migrationBuilder.DropTable( + name: "people_roles"); + + migrationBuilder.DropTable( + name: "watched_episode"); + + migrationBuilder.DropTable( + name: "collections"); + + migrationBuilder.DropTable( + name: "movies"); + + migrationBuilder.DropTable( + name: "people"); + + migrationBuilder.DropTable( + name: "episodes"); + + migrationBuilder.DropTable( + name: "users"); + + migrationBuilder.DropTable( + name: "seasons"); + + migrationBuilder.DropTable( + name: "shows"); + + migrationBuilder.DropTable( + name: "studios"); + } + } +} diff --git a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index 69fc5caf..93540795 100644 --- a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -1,6 +1,5 @@ // using System; -using System.Collections.Generic; using Kyoo.Abstractions.Models; using Kyoo.Postgresql; using Microsoft.EntityFrameworkCore; @@ -22,10 +21,29 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("ProductVersion", "7.0.9") .HasAnnotation("Relational:MaxIdentifierLength", 63); - NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_type", new[] { "show", "movie", "collection" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "action", "adventure", "animation", "comedy", "crime", "documentary", "drama", "family", "fantasy", "history", "horror", "music", "mystery", "romance", "science_fiction", "thriller", "war", "western" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("CollectionMovie", b => + { + b.Property("CollectionsID") + .HasColumnType("integer") + .HasColumnName("collections_id"); + + b.Property("MoviesID") + .HasColumnType("integer") + .HasColumnName("movies_id"); + + b.HasKey("CollectionsID", "MoviesID") + .HasName("pk_collection_movie"); + + b.HasIndex("MoviesID") + .HasDatabaseName("ix_collection_movie_movies_id"); + + b.ToTable("collection_movie", (string)null); + }); + modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => { b.Property("ID") @@ -35,11 +53,13 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); b.Property("Name") + .IsRequired() .HasColumnType("text") .HasColumnName("name"); @@ -49,7 +69,8 @@ namespace Kyoo.Postgresql.Migrations b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.HasKey("ID") @@ -79,15 +100,21 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("episode_number"); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); b.Property("Path") + .IsRequired() .HasColumnType("text") .HasColumnName("path"); @@ -109,13 +136,10 @@ namespace Kyoo.Postgresql.Migrations b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); - b.HasKey("ID") .HasName("pk_episodes"); @@ -133,7 +157,7 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("episodes", (string)null); }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Genre", b => + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -142,97 +166,77 @@ namespace Kyoo.Postgresql.Migrations 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") + b.Property("AirDate") .HasColumnType("timestamp with time zone") - .HasColumnName("end_air"); + .HasColumnName("air_date"); + + b.Property("Aliases") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Slug") + b.Property("Path") + .IsRequired() .HasColumnType("text") + .HasColumnName("path"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.Property("StartAir") - .HasColumnType("timestamp with time zone") - .HasColumnName("start_air"); - - b.Property("Status") + b.Property("Status") .HasColumnType("status") .HasColumnName("status"); - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); + b.Property("StudioID") + .HasColumnType("integer") + .HasColumnName("studio_id"); - b.Property("Type") - .HasColumnType("item_type") - .HasColumnName("type"); + b.Property("Tagline") + .HasColumnType("text") + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); + + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); b.HasKey("ID") - .HasName("pk_library_items"); + .HasName("pk_movies"); - b.ToTable((string)null); + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_movies_slug"); - b.ToView("library_items", (string)null); + b.HasIndex("StudioID") + .HasDatabaseName("ix_movies_studio_id"); + + b.ToTable("movies", (string)null); }); modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => @@ -244,17 +248,20 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); b.Property("Name") + .IsRequired() .HasColumnType("text") .HasColumnName("name"); b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.HasKey("ID") @@ -276,25 +283,34 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + b.Property("MovieID") + .HasColumnType("integer") + .HasColumnName("movie_id"); + b.Property("PeopleID") .HasColumnType("integer") .HasColumnName("people_id"); b.Property("Role") + .IsRequired() .HasColumnType("text") .HasColumnName("role"); - b.Property("ShowID") + b.Property("ShowID") .HasColumnType("integer") .HasColumnName("show_id"); b.Property("Type") + .IsRequired() .HasColumnType("text") .HasColumnName("type"); b.HasKey("ID") .HasName("pk_people_roles"); + b.HasIndex("MovieID") + .HasDatabaseName("ix_people_roles_movie_id"); + b.HasIndex("PeopleID") .HasDatabaseName("ix_people_roles_people_id"); @@ -317,9 +333,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("end_date"); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); b.Property("Overview") .HasColumnType("text") @@ -335,17 +356,14 @@ namespace Kyoo.Postgresql.Migrations b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .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"); @@ -370,6 +388,7 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); b.Property("Aliases") + .IsRequired() .HasColumnType("text[]") .HasColumnName("aliases"); @@ -377,25 +396,29 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("end_air"); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); - b.Property("IsMovie") - .HasColumnType("boolean") - .HasColumnName("is_movie"); + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); b.Property("Overview") .HasColumnType("text") .HasColumnName("overview"); - b.Property("Path") - .HasColumnType("text") - .HasColumnName("path"); - b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.Property("StartAir") @@ -410,9 +433,14 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("integer") .HasColumnName("studio_id"); - b.Property("Title") + b.Property("Tagline") .HasColumnType("text") - .HasColumnName("title"); + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); b.Property("Trailer") .HasColumnType("text") @@ -440,17 +468,20 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); - b.Property("ExternalIDs") + b.Property("ExternalId") + .IsRequired() .HasColumnType("json") - .HasColumnName("external_i_ds"); + .HasColumnName("external_id"); b.Property("Name") + .IsRequired() .HasColumnType("text") .HasColumnName("name"); b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.HasKey("ID") @@ -473,27 +504,28 @@ namespace Kyoo.Postgresql.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); b.Property("Email") + .IsRequired() .HasColumnType("text") .HasColumnName("email"); - b.Property>("ExtraData") - .HasColumnType("jsonb") - .HasColumnName("extra_data"); - b.Property("Password") + .IsRequired() .HasColumnType("text") .HasColumnName("password"); b.Property("Permissions") + .IsRequired() .HasColumnType("text[]") .HasColumnName("permissions"); b.Property("Slug") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("slug"); b.Property("Username") + .IsRequired() .HasColumnType("text") .HasColumnName("username"); @@ -522,12 +554,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("watched_percentage"); b.HasKey("UserID", "EpisodeID") - .HasName("pk_watched_episodes"); + .HasName("pk_watched_episode"); b.HasIndex("EpisodeID") - .HasDatabaseName("ix_watched_episodes_episode_id"); + .HasDatabaseName("ix_watched_episode_episode_id"); - b.ToTable("watched_episodes", (string)null); + b.ToTable("watched_episode", (string)null); }); modelBuilder.Entity("ShowUser", b => @@ -568,61 +600,21 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("link_collection_show", (string)null); }); - modelBuilder.Entity("link_library_collection", b => + modelBuilder.Entity("CollectionMovie", b => { - b.Property("collection_id") - .HasColumnType("integer") - .HasColumnName("collection_id"); + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_collection_movie_collections_collections_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); + b.HasOne("Kyoo.Abstractions.Models.Movie", null) + .WithMany() + .HasForeignKey("MoviesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_collection_movie_movies_movies_id"); }); modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => @@ -634,11 +626,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -658,11 +652,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -682,11 +678,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -728,11 +726,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -752,11 +752,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -776,11 +778,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -804,87 +808,98 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Thumbnail"); }); - modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { + b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") + .WithMany("Movies") + .HasForeignKey("StudioID") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_movies_studios_studio_id"); + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("LibraryItemID") + b1.Property("MovieID") .HasColumnType("integer") - .HasColumnName("library_item_id"); + .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") - .HasColumnName("blurhash"); + .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") - .HasColumnName("source"); + .HasColumnName("logo_source"); - b1.HasKey("LibraryItemID"); + b1.HasKey("MovieID"); - b1.ToTable((string)null); - - b1.ToView("library_items"); + b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("LibraryItemID"); + .HasForeignKey("MovieID") + .HasConstraintName("fk_movies_movies_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("LibraryItemID") + b1.Property("MovieID") .HasColumnType("integer") - .HasColumnName("library_item_id"); + .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") - .HasColumnName("blurhash"); + .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") - .HasColumnName("source"); + .HasColumnName("poster_source"); - b1.HasKey("LibraryItemID"); + b1.HasKey("MovieID"); - b1.ToTable((string)null); - - b1.ToView("library_items"); + b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("LibraryItemID"); + .HasForeignKey("MovieID") + .HasConstraintName("fk_movies_movies_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("LibraryItemID") + b1.Property("MovieID") .HasColumnType("integer") - .HasColumnName("library_item_id"); + .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") - .HasColumnName("blurhash"); + .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") - .HasColumnName("source"); + .HasColumnName("thumbnail_source"); - b1.HasKey("LibraryItemID"); + b1.HasKey("MovieID"); - b1.ToTable((string)null); - - b1.ToView("library_items"); + b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("LibraryItemID"); + .HasForeignKey("MovieID") + .HasConstraintName("fk_movies_movies_id"); }); b.Navigation("Logo"); b.Navigation("Poster"); + b.Navigation("Studio"); + b.Navigation("Thumbnail"); }); @@ -897,11 +912,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -921,11 +938,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -945,11 +964,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -971,6 +992,11 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => { + b.HasOne("Kyoo.Abstractions.Models.Movie", "Movie") + .WithMany("People") + .HasForeignKey("MovieID") + .HasConstraintName("fk_people_roles_movies_movie_id"); + b.HasOne("Kyoo.Abstractions.Models.People", "People") .WithMany("Roles") .HasForeignKey("PeopleID") @@ -981,10 +1007,10 @@ namespace Kyoo.Postgresql.Migrations b.HasOne("Kyoo.Abstractions.Models.Show", "Show") .WithMany("People") .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() .HasConstraintName("fk_people_roles_shows_show_id"); + b.Navigation("Movie"); + b.Navigation("People"); b.Navigation("Show"); @@ -1006,11 +1032,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -1030,11 +1058,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -1054,11 +1084,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -1095,11 +1127,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -1119,11 +1153,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("poster_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("poster_source"); @@ -1143,11 +1179,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("thumbnail_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("thumbnail_source"); @@ -1178,11 +1216,13 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("id"); b1.Property("Blurhash") + .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("logo_blurhash"); b1.Property("Source") + .IsRequired() .HasColumnType("text") .HasColumnName("logo_source"); @@ -1205,14 +1245,14 @@ namespace Kyoo.Postgresql.Migrations .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_watched_episodes_episodes_episode_id"); + .HasConstraintName("fk_watched_episode_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"); + .HasConstraintName("fk_watched_episode_users_user_id"); b.Navigation("Episode"); }); @@ -1251,55 +1291,9 @@ namespace Kyoo.Postgresql.Migrations .HasConstraintName("fk_link_collection_show_shows_show_id"); }); - modelBuilder.Entity("link_library_collection", b => + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", 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"); + b.Navigation("People"); }); modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => @@ -1323,6 +1317,8 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => { + b.Navigation("Movies"); + b.Navigation("Shows"); }); diff --git a/back/src/Kyoo.Postgresql/PostgresContext.cs b/back/src/Kyoo.Postgresql/PostgresContext.cs index cff6167d..dc2918ea 100644 --- a/back/src/Kyoo.Postgresql/PostgresContext.cs +++ b/back/src/Kyoo.Postgresql/PostgresContext.cs @@ -43,12 +43,12 @@ namespace Kyoo.Postgresql /// private readonly bool _skipConfigure; - // TOOD: This needs ot be updated but ef-core still does not offer a way to use this. + // TODO: This needs ot be updated but ef-core still does not offer a way to use this. [Obsolete] static PostgresContext() { NpgsqlConnection.GlobalTypeMapper.MapEnum(); - NpgsqlConnection.GlobalTypeMapper.MapEnum(); + NpgsqlConnection.GlobalTypeMapper.MapEnum(); } /// @@ -100,26 +100,11 @@ namespace Kyoo.Postgresql protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasPostgresEnum(); - modelBuilder.HasPostgresEnum(); - - modelBuilder.Entity() - .ToView("library_items") - .HasKey(x => x.ID); - - modelBuilder.Entity() - .Property(x => x.ExtraData) - .HasColumnType("jsonb"); + modelBuilder.HasPostgresEnum(); base.OnModelCreating(modelBuilder); } - /// - protected override string MetadataName() - { - SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture); - return rewriter.RewriteName(typeof(T).Name + nameof(MetadataID)); - } - /// protected override string LinkName() { diff --git a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs index 476dc0b6..aa2fee16 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs @@ -37,21 +37,17 @@ namespace Kyoo.Tests.Database { Context = new PostgresTestContext(postgres, output); - LibraryRepository library = new(_NewContext()); CollectionRepository collection = new(_NewContext()); - GenreRepository genre = new(_NewContext()); StudioRepository studio = new(_NewContext()); PeopleRepository people = new(_NewContext(), new Lazy(() => LibraryManager.ShowRepository)); - ShowRepository show = new(_NewContext(), studio, people, genre); + ShowRepository show = new(_NewContext(), studio, people); SeasonRepository season = new(_NewContext(), show); - LibraryItemRepository libraryItem = new(_NewContext(), - new Lazy(() => LibraryManager.LibraryRepository)); + LibraryItemRepository libraryItem = new(_NewContext()); EpisodeRepository episode = new(_NewContext(), show); UserRepository user = new(_NewContext()); LibraryManager = new LibraryManager(new IBaseRepository[] { - library, libraryItem, collection, show, @@ -59,7 +55,6 @@ namespace Kyoo.Tests.Database episode, people, studio, - genre, user }); } diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index 1fc78b23..6e3a6b18 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -234,7 +234,7 @@ namespace Kyoo.Tests.Database public async Task EditTest() { Episode value = await _repository.Get(TestSample.Get().Slug); - value.Title = "New Title"; + value.Name = "New Title"; value.Images = new Dictionary { [Images.Poster] = "new-poster" @@ -325,7 +325,7 @@ namespace Kyoo.Tests.Database { Episode value = new() { - Title = "This is a test super title", + Name = "This is a test super title", ShowID = 1, AbsoluteNumber = 2 }; diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index 7a336183..c1107617 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -121,7 +121,7 @@ namespace Kyoo.Tests.Database public async Task EditTest() { Season value = await _repository.Get(TestSample.Get().Slug); - value.Title = "New Title"; + value.Name = "New Title"; value.Images = new Dictionary { [Images.Poster] = "new-poster" @@ -212,7 +212,7 @@ namespace Kyoo.Tests.Database { Season value = new() { - Title = "This is a test super title", + Name = "This is a test super title", ShowID = 1 }; await _repository.Create(value); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index 77895d8f..b8a40ae5 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -56,7 +56,7 @@ namespace Kyoo.Tests.Database { Show value = await _repository.Get(TestSample.Get().Slug); value.Path = "/super"; - value.Title = "New Title"; + value.Name = "New Title"; Show edited = await _repository.Edit(value, false); KAssert.DeepEqual(value, edited); @@ -215,7 +215,7 @@ namespace Kyoo.Tests.Database { ID = value.ID, Slug = "reset", - Title = "Reset" + Name = "Reset" }; Show edited = await _repository.Edit(newValue, true); @@ -223,7 +223,7 @@ namespace Kyoo.Tests.Database Assert.Equal(value.ID, edited.ID); Assert.Null(edited.Overview); Assert.Equal("reset", edited.Slug); - Assert.Equal("Reset", edited.Title); + Assert.Equal("Reset", edited.Name); Assert.Null(edited.Aliases); Assert.Null(edited.ExternalId); Assert.Null(edited.People); @@ -348,7 +348,7 @@ namespace Kyoo.Tests.Database Show value = new() { Slug = "super-test", - Title = "This is a test title?" + Name = "This is a test title?" }; await _repository.Create(value); ICollection ret = await _repository.Search(query); diff --git a/back/tests/Kyoo.Tests/Database/TestSample.cs b/back/tests/Kyoo.Tests/Database/TestSample.cs index 0971de68..5fbe23a8 100644 --- a/back/tests/Kyoo.Tests/Database/TestSample.cs +++ b/back/tests/Kyoo.Tests/Database/TestSample.cs @@ -27,16 +27,6 @@ namespace Kyoo.Tests { private static readonly Dictionary> NewSamples = new() { - { - typeof(Library), - () => new Library - { - ID = 2, - Slug = "new-library", - Name = "New Library", - Paths = new[] { "/a/random/path" } - } - }, { typeof(Collection), () => new Collection @@ -57,7 +47,7 @@ namespace Kyoo.Tests { ID = 2, Slug = "new-show", - Title = "New Show", + Name = "New Show", Overview = "overview", Status = Status.Planned, StartAir = new DateTime(2011, 1, 1).ToUniversalTime(), @@ -79,7 +69,7 @@ namespace Kyoo.Tests ID = 2, ShowID = 1, ShowSlug = Get().Slug, - Title = "New season", + Name = "New season", Overview = "New overview", EndDate = new DateTime(2000, 10, 10).ToUniversalTime(), SeasonNumber = 2, @@ -102,7 +92,7 @@ namespace Kyoo.Tests EpisodeNumber = 3, AbsoluteNumber = 4, Path = "/episode-path", - Title = "New Episode Title", + Name = "New Episode Title", ReleaseDate = new DateTime(2000, 10, 10).ToUniversalTime(), Overview = "new episode overview", Images = new Dictionary @@ -172,7 +162,7 @@ namespace Kyoo.Tests { ID = 1, Slug = "anohana", - Title = "Anohana: The Flower We Saw That Day", + Name = "Anohana: The Flower We Saw That Day", Aliases = new[] { "Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.", @@ -204,7 +194,7 @@ namespace Kyoo.Tests ShowSlug = "anohana", ShowID = 1, SeasonNumber = 1, - Title = "Season 1", + Name = "Season 1", Overview = "The first season", StartDate = new DateTime(2020, 06, 05).ToUniversalTime(), EndDate = new DateTime(2020, 07, 05).ToUniversalTime(), @@ -234,7 +224,7 @@ namespace Kyoo.Tests [Images.Logo] = "Logo", [Images.Thumbnail] = "Thumbnail" }, - Title = "Episode 1", + Name = "Episode 1", Overview = "Summary of the first episode", ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() } @@ -379,7 +369,7 @@ namespace Kyoo.Tests [Images.Logo] = "Logo", [Images.Thumbnail] = "Thumbnail" }, - Title = "Episode 3", + Name = "Episode 3", Overview = "Summary of the third absolute episode", ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() }; @@ -399,7 +389,7 @@ namespace Kyoo.Tests [Images.Logo] = "Logo", [Images.Thumbnail] = "Thumbnail" }, - Title = "John wick", + Name = "John wick", Overview = "A movie episode test", ReleaseDate = new DateTime(1595, 05, 12).ToUniversalTime() }; diff --git a/scanner/providers/implementations/themoviedatabase.py b/scanner/providers/implementations/themoviedatabase.py index e72d8cc7..2b86dda5 100644 --- a/scanner/providers/implementations/themoviedatabase.py +++ b/scanner/providers/implementations/themoviedatabase.py @@ -123,7 +123,7 @@ class TheMovieDatabase(Provider): ret = Movie( original_language=movie["original_language"], aliases=[x["title"] for x in movie["alternative_titles"]["titles"]], - release_date=datetime.strptime(movie["release_date"], "%Y-%m-%d").date() + air_date=datetime.strptime(movie["release_date"], "%Y-%m-%d").date() if movie["release_date"] else None, status=MovieStatus.FINISHED @@ -148,8 +148,8 @@ class TheMovieDatabase(Provider): ) translation = MovieTranslation( name=movie["title"], - tagline=movie["tagline"], - keywords=list(map(lambda x: x["name"], movie["keywords"]["keywords"])), + tagline=movie["tagline"] if movie["tagline"] else None, + tags=list(map(lambda x: x["name"], movie["keywords"]["keywords"])), overview=movie["overview"], posters=self.get_image(movie["images"]["posters"]), logos=self.get_image(movie["images"]["logos"]), @@ -224,8 +224,8 @@ class TheMovieDatabase(Provider): ) translation = ShowTranslation( name=show["name"], - tagline=show["tagline"], - keywords=list(map(lambda x: x["name"], show["keywords"]["results"])), + tagline=show["tagline"] if show["tagline"] else None, + tags=list(map(lambda x: x["name"], show["keywords"]["results"])), overview=show["overview"], posters=self.get_image(show["images"]["posters"]), logos=self.get_image(show["images"]["logos"]), diff --git a/scanner/providers/types/episode.py b/scanner/providers/types/episode.py index 4d2bb676..72723b13 100644 --- a/scanner/providers/types/episode.py +++ b/scanner/providers/types/episode.py @@ -40,9 +40,9 @@ class Episode: return { **asdict(self), **asdict(self.translations[default_language]), - "title": self.translations[default_language].name, - "images": { - "1": self.thumbnail, - }, + # "poster": next(iter(self.translations[default_language].posters), None), + # "thumbnail": next(iter(self.translations[default_language].thumbnails), None), + # "logo": next(iter(self.translations[default_language].logos), None), + "thumbnail": None, "show": None, } diff --git a/scanner/providers/types/genre.py b/scanner/providers/types/genre.py index 79c8c3ed..287c07a4 100644 --- a/scanner/providers/types/genre.py +++ b/scanner/providers/types/genre.py @@ -16,10 +16,10 @@ class Genre(str, Enum): MUSIC = "Music" MYSTERY = "Mystery" ROMANCE = "Romance" - SCIENCE_FICTION = "Science Fiction" + SCIENCE_FICTION = "ScienceFiction" THRILLER = "Thriller" WAR = "War" WESTERN = "Western" def to_kyoo(self): - return {"name": self.value} + return self.value diff --git a/scanner/providers/types/movie.py b/scanner/providers/types/movie.py index d0963153..c4e7eeb4 100644 --- a/scanner/providers/types/movie.py +++ b/scanner/providers/types/movie.py @@ -20,7 +20,7 @@ class Status(str, Enum): class MovieTranslation: name: str tagline: Optional[str] = None - keywords: list[str] = field(default_factory=list) + tags: list[str] = field(default_factory=list) overview: Optional[str] = None posters: list[str] = field(default_factory=list) @@ -33,7 +33,7 @@ class MovieTranslation: class Movie: original_language: Optional[str] = None aliases: list[str] = field(default_factory=list) - release_date: Optional[date | int] = None + air_date: Optional[date | int] = None status: Status = Status.UNKNOWN path: Optional[str] = None studios: list[Studio] = field(default_factory=list) @@ -50,18 +50,10 @@ class Movie: return { **asdict(self), **asdict(self.translations[default_language]), - "images": { - "0": next(iter(self.translations[default_language].posters), None), - "1": next(iter(self.translations[default_language].thumbnails), None), - "2": next(iter(self.translations[default_language].logos), None), - "3": next(iter(self.translations[default_language].trailers), None), - }, + # "poster": next(iter(self.translations[default_language].posters), None), + # "thumbnail": next(iter(self.translations[default_language].thumbnails), None), + # "logo": next(iter(self.translations[default_language].logos), None), + "trailer": next(iter(self.translations[default_language].trailers), None), "studio": next((x.to_kyoo() for x in self.studios), None), - "release_date": None, - "startAir": format_date(self.release_date), - "title": self.translations[default_language].name, "genres": [x.to_kyoo() for x in self.genres], - "isMovie": True, - # TODO: The back has bad external id support, we disable it for now - "external_ids": None, } diff --git a/scanner/providers/types/season.py b/scanner/providers/types/season.py index 92e3dd7e..b4e68170 100644 --- a/scanner/providers/types/season.py +++ b/scanner/providers/types/season.py @@ -30,11 +30,7 @@ class Season: return { **asdict(self), **asdict(self.translations[default_language]), - "images": { - "0": next(iter(self.translations[default_language].posters), None), - "1": next(iter(self.translations[default_language].thumbnails), None), - }, - "title": self.translations[default_language].name, - # TODO: The back has bad external id support, we disable it for now - "external_ids": None, + # "poster": next(iter(self.translations[default_language].posters), None), + # "thumbnail": next(iter(self.translations[default_language].thumbnails), None), + # "logo": next(iter(self.translations[default_language].logos), None), } diff --git a/scanner/providers/types/show.py b/scanner/providers/types/show.py index cf3258b5..a7229519 100644 --- a/scanner/providers/types/show.py +++ b/scanner/providers/types/show.py @@ -21,7 +21,7 @@ class Status(str, Enum): class ShowTranslation: name: str tagline: Optional[str] - keywords: list[str] + tags: list[str] overview: Optional[str] posters: list[str] @@ -52,16 +52,11 @@ class Show: return { **asdict(self), **asdict(self.translations[default_language]), - "images": { - "0": next(iter(self.translations[default_language].posters), None), - "1": next(iter(self.translations[default_language].thumbnails), None), - "2": next(iter(self.translations[default_language].logos), None), - "3": next(iter(self.translations[default_language].trailers), None), - }, "studio": next((x.to_kyoo() for x in self.studios), None), - "title": self.translations[default_language].name, - "genres": [x.to_kyoo() for x in self.genres], "seasons": None, - # TODO: The back has bad external id support, we disable it for now - "external_ids": None, + # "poster": next(iter(self.translations[default_language].posters), None), + # "thumbnail": next(iter(self.translations[default_language].thumbnails), None), + # "logo": next(iter(self.translations[default_language].logos), None), + "trailer": next(iter(self.translations[default_language].trailers), None), + "genres": [x.to_kyoo() for x in self.genres], } diff --git a/scanner/providers/types/studio.py b/scanner/providers/types/studio.py index d952b63d..7f7b3149 100644 --- a/scanner/providers/types/studio.py +++ b/scanner/providers/types/studio.py @@ -12,9 +12,5 @@ class Studio: def to_kyoo(self): return { **asdict(self), - "images": { - "2": next(iter(self.logos), None), - }, - # TODO: The back has bad external id support, we disable it for now - "external_ids": None, + # "logo": next(iter(self.logos), None), } diff --git a/scanner/scanner/scanner.py b/scanner/scanner/scanner.py index e7a3e432..babda259 100644 --- a/scanner/scanner/scanner.py +++ b/scanner/scanner/scanner.py @@ -48,7 +48,7 @@ class Scanner: await asyncio.gather(*map(self.identify, group)) async def get_registered_paths(self) -> List[str]: - # TODO: Once movies are separated from the api, a new endpoint should be created to check for paths. + paths = None async with self._client.get( f"{self._url}/episodes", params={"limit": 0}, @@ -56,7 +56,17 @@ class Scanner: ) as r: r.raise_for_status() ret = await r.json() - return list(x["path"] for x in ret["items"]) + paths = list(x["path"] for x in ret["items"]) + + async with self._client.get( + f"{self._url}/movies", + params={"limit": 0}, + headers={"X-API-Key": self._api_key}, + ) as r: + r.raise_for_status() + ret = await r.json() + paths += list(x["path"] for x in ret["items"]) + return paths; @log_errors async def identify(self, path: str): @@ -157,7 +167,13 @@ class Scanner: async def delete(self, path: str): logging.info("Deleting %s", path) - # TODO: Adapt this for movies as well when they are split + async with self._client.delete( + f"{self._url}/movies?path={path}", headers={"X-API-Key": self._api_key} + ) as r: + if not r.ok: + logging.error(f"Request error: {await r.text()}") + r.raise_for_status() + async with self._client.delete( f"{self._url}/episodes?path={path}", headers={"X-API-Key": self._api_key} ) as r: diff --git a/shell.nix b/shell.nix index a2f69d2f..877ae085 100644 --- a/shell.nix +++ b/shell.nix @@ -24,6 +24,7 @@ in openssl mediainfo ffmpeg + postgresql ]; RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; From 5c270a0362863236213cd7c5903c37f11189ae11 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 5 Aug 2023 15:35:49 +0900 Subject: [PATCH 03/37] Handle external ids --- .../Kyoo.Abstractions/Models/LibraryItem.cs | 2 +- .../Kyoo.Abstractions/Models/MetadataID.cs | 6 +- back/src/Kyoo.Abstractions/Models/Page.cs | 4 +- .../Kyoo.Abstractions/Models/PeopleRole.cs | 2 +- .../Models/Resources/Collection.cs | 4 +- .../Models/Resources/Episode.cs | 4 +- .../Models/Resources/Interfaces/IMetadata.cs | 4 +- .../Models/Resources/Interfaces/IResource.cs | 2 +- .../Models/Resources/Movie.cs | 4 +- .../Models/Resources/People.cs | 4 +- .../Models/Resources/Season.cs | 4 +- .../Models/Resources/Show.cs | 4 +- .../Models/Resources/Studio.cs | 4 +- .../Models/Resources/User.cs | 2 +- .../Models/Utils/Identifier.cs | 4 +- .../Controllers/TokenController.cs | 4 +- back/src/Kyoo.Authentication/Views/AuthApi.cs | 4 +- .../Kyoo.Core/Controllers/LibraryManager.cs | 38 ++-- .../Repositories/EpisodeRepository.cs | 6 +- .../Repositories/LibraryItemRepository.cs | 2 +- .../Repositories/LocalRepository.cs | 10 +- .../Repositories/MovieRepository.cs | 4 +- .../Repositories/PeopleRepository.cs | 2 +- .../Repositories/SeasonRepository.cs | 6 +- .../Repositories/ShowRepository.cs | 6 +- back/src/Kyoo.Core/Views/Helper/CrudApi.cs | 8 +- back/src/Kyoo.Postgresql/DatabaseContext.cs | 10 +- ....cs => 20230805052627_initial.Designer.cs} | 194 +++++++++--------- ...0_initial.cs => 20230805052627_initial.cs} | 0 .../PostgresContextModelSnapshot.cs | 192 ++++++++--------- .../Kyoo.Tests/Database/RepositoryTests.cs | 10 +- .../SpecificTests/CollectionsTests.cs | 20 +- .../Database/SpecificTests/EpisodeTests.cs | 46 ++--- .../Database/SpecificTests/PeopleTests.cs | 20 +- .../Database/SpecificTests/SeasonTests.cs | 26 +-- .../Database/SpecificTests/ShowTests.cs | 30 +-- back/tests/Kyoo.Tests/Database/TestSample.cs | 42 ++-- back/tests/Kyoo.Tests/Utility/MergerTests.cs | 8 +- back/tests/Kyoo.Tests/Utility/UtilityTests.cs | 8 +- .../implementations/themoviedatabase.py | 39 ++-- scanner/providers/types/episode.py | 4 +- scanner/providers/types/metadataid.py | 2 +- scanner/providers/types/movie.py | 2 +- scanner/providers/types/season.py | 2 +- scanner/providers/types/show.py | 2 +- scanner/providers/types/studio.py | 2 +- scanner/scanner/__init__.py | 1 + scanner/scanner/scanner.py | 2 +- 48 files changed, 408 insertions(+), 398 deletions(-) rename back/src/Kyoo.Postgresql/Migrations/{20230805051120_initial.Designer.cs => 20230805052627_initial.Designer.cs} (90%) rename back/src/Kyoo.Postgresql/Migrations/{20230805051120_initial.cs => 20230805052627_initial.cs} (100%) diff --git a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs index 0b66d2e1..677f27ca 100644 --- a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs +++ b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs @@ -64,7 +64,7 @@ namespace Kyoo.Abstractions.Models { public ItemKind Kind { get; } - public int ID { get; set; } + public int Id { get; set; } public string Slug { get; set; } diff --git a/back/src/Kyoo.Abstractions/Models/MetadataID.cs b/back/src/Kyoo.Abstractions/Models/MetadataID.cs index 0ab9b8fb..ba9ba13b 100644 --- a/back/src/Kyoo.Abstractions/Models/MetadataID.cs +++ b/back/src/Kyoo.Abstractions/Models/MetadataID.cs @@ -20,16 +20,16 @@ namespace Kyoo.Abstractions.Models /// /// ID and link of an item on an external provider. /// - public class MetadataID + public class MetadataId { /// /// The ID of the resource on the external provider. /// - public string DataID { get; set; } + public string DataId { get; set; } /// /// The URL of the resource on the external provider. /// - public string Link { get; set; } + public string? Link { get; set; } } } diff --git a/back/src/Kyoo.Abstractions/Models/Page.cs b/back/src/Kyoo.Abstractions/Models/Page.cs index 6c09e54a..55fc9a5e 100644 --- a/back/src/Kyoo.Abstractions/Models/Page.cs +++ b/back/src/Kyoo.Abstractions/Models/Page.cs @@ -93,14 +93,14 @@ namespace Kyoo.Abstractions.Models if (items.Count > 0 && query.ContainsKey("afterID")) { - query["afterID"] = items.First().ID.ToString(); + query["afterID"] = items.First().Id.ToString(); query["reverse"] = "true"; Previous = url + query.ToQueryString(); } query.Remove("reverse"); if (items.Count == limit && limit > 0) { - query["afterID"] = items.Last().ID.ToString(); + query["afterID"] = items.Last().Id.ToString(); Next = url + query.ToQueryString(); } diff --git a/back/src/Kyoo.Abstractions/Models/PeopleRole.cs b/back/src/Kyoo.Abstractions/Models/PeopleRole.cs index dab757a1..33d3b92d 100644 --- a/back/src/Kyoo.Abstractions/Models/PeopleRole.cs +++ b/back/src/Kyoo.Abstractions/Models/PeopleRole.cs @@ -29,7 +29,7 @@ namespace Kyoo.Abstractions.Models public class PeopleRole : IResource { /// - public int ID { get; set; } + public int Id { get; set; } /// public string Slug => ForPeople ? Show.Slug : People.Slug; diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs index a6f3043f..544e6a17 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs @@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Models public class Collection : IResource, IMetadata, IThumbnails { /// - public int ID { get; set; } + public int Id { get; set; } /// [MaxLength(256)] public string Slug { get; set; } @@ -66,7 +66,7 @@ namespace Kyoo.Abstractions.Models [LoadableRelation] public ICollection? Shows { get; set; } /// - public Dictionary ExternalId { get; set; } = new(); + public Dictionary ExternalId { get; set; } = new(); public Collection() { } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index 87b20a5e..a226c8a8 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -32,7 +32,7 @@ namespace Kyoo.Abstractions.Models public class Episode : IResource, IMetadata, IThumbnails { /// - public int ID { get; set; } + public int Id { get; set; } /// [Computed] @@ -148,7 +148,7 @@ namespace Kyoo.Abstractions.Models public Image? Logo { get; set; } /// - public Dictionary ExternalId { get; set; } = new(); + public Dictionary ExternalId { get; set; } = new(); /// /// 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 d46a029e..9cfb2595 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs @@ -26,8 +26,8 @@ namespace Kyoo.Abstractions.Models public interface IMetadata { /// - /// The link to metadata providers that this show has. See for more information. + /// The link to metadata providers that this show has. See for more information. /// - public Dictionary ExternalId { get; set; } + public Dictionary ExternalId { get; set; } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs index 69e77f21..925e38f5 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs @@ -33,7 +33,7 @@ namespace Kyoo.Abstractions.Models /// You don't need to specify an ID manually when creating a new resource, /// this field is automatically assigned by the . /// - public int ID { get; set; } + public int Id { get; set; } /// /// A human-readable identifier that can be used instead of an ID. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index a6f8ec27..13943ff2 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -32,7 +32,7 @@ namespace Kyoo.Abstractions.Models public class Movie : IResource, IMetadata, IOnMerge, IThumbnails { /// - public int ID { get; set; } + public int Id { get; set; } /// [MaxLength(256)] @@ -98,7 +98,7 @@ namespace Kyoo.Abstractions.Models public string? Trailer { get; set; } /// - public Dictionary ExternalId { get; set; } = new(); + public Dictionary ExternalId { get; set; } = new(); /// /// The ID of the Studio that made this show. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/People.cs b/back/src/Kyoo.Abstractions/Models/Resources/People.cs index ed56d5e5..51d70f6c 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/People.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/People.cs @@ -30,7 +30,7 @@ namespace Kyoo.Abstractions.Models public class People : IResource, IMetadata, IThumbnails { /// - public int ID { get; set; } + public int Id { get; set; } /// [MaxLength(256)] @@ -51,7 +51,7 @@ namespace Kyoo.Abstractions.Models public Image? Logo { get; set; } /// - public Dictionary ExternalId { get; set; } = new(); + public Dictionary ExternalId { get; set; } = new(); /// /// The list of roles this person has played in. See for more information. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs index 0d0e0130..b1eacec4 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs @@ -32,7 +32,7 @@ namespace Kyoo.Abstractions.Models public class Season : IResource, IMetadata, IThumbnails { /// - public int ID { get; set; } + public int Id { get; set; } /// [Computed] @@ -110,7 +110,7 @@ namespace Kyoo.Abstractions.Models public Image? Logo { get; set; } /// - public Dictionary ExternalId { get; set; } = new(); + public Dictionary ExternalId { get; set; } = new(); /// /// 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 30da3693..a73b8b21 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -32,7 +32,7 @@ namespace Kyoo.Abstractions.Models public class Show : IResource, IMetadata, IOnMerge, IThumbnails { /// - public int ID { get; set; } + public int Id { get; set; } /// [MaxLength(256)] @@ -100,7 +100,7 @@ namespace Kyoo.Abstractions.Models public string? Trailer { get; set; } /// - public Dictionary ExternalId { get; set; } = new(); + public Dictionary ExternalId { get; set; } = new(); /// /// 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 ab61b149..c1071766 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Studio.cs @@ -30,7 +30,7 @@ namespace Kyoo.Abstractions.Models public class Studio : IResource, IMetadata { /// - public int ID { get; set; } + public int Id { get; set; } /// [MaxLength(256)] @@ -52,7 +52,7 @@ namespace Kyoo.Abstractions.Models [LoadableRelation] public ICollection? Movies { get; set; } /// - public Dictionary ExternalId { get; set; } = new(); + public Dictionary ExternalId { get; set; } = new(); /// /// 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 3b5168a7..647e4945 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/User.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/User.cs @@ -30,7 +30,7 @@ namespace Kyoo.Abstractions.Models public class User : IResource { /// - public int ID { get; set; } + public int Id { get; set; } /// [MaxLength(256)] diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs index 97c8c8e9..2bbcfc5b 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs @@ -139,7 +139,7 @@ namespace Kyoo.Abstractions.Models.Utils public bool IsSame(IResource resource) { return Match( - id => resource.ID == id, + id => resource.Id == id, slug => resource.Slug == slug ); } @@ -155,7 +155,7 @@ namespace Kyoo.Abstractions.Models.Utils where T : IResource { return _id.HasValue - ? x => x.ID == _id.Value + ? x => x.Id == _id.Value : x => x.Slug == _slug; } diff --git a/back/src/Kyoo.Authentication/Controllers/TokenController.cs b/back/src/Kyoo.Authentication/Controllers/TokenController.cs index 524b5a91..64a9f9cd 100644 --- a/back/src/Kyoo.Authentication/Controllers/TokenController.cs +++ b/back/src/Kyoo.Authentication/Controllers/TokenController.cs @@ -61,7 +61,7 @@ namespace Kyoo.Authentication : string.Empty; List claims = new() { - new Claim(Claims.Id, user.ID.ToString(CultureInfo.InvariantCulture)), + new Claim(Claims.Id, user.Id.ToString(CultureInfo.InvariantCulture)), new Claim(Claims.Name, user.Username), new Claim(Claims.Permissions, permissions), new Claim(Claims.Type, "access") @@ -85,7 +85,7 @@ namespace Kyoo.Authentication signingCredentials: credential, claims: new[] { - new Claim(Claims.Id, user.ID.ToString(CultureInfo.InvariantCulture)), + new Claim(Claims.Id, user.Id.ToString(CultureInfo.InvariantCulture)), new Claim(Claims.Guid, Guid.NewGuid().ToString()), new Claim(Claims.Type, "refresh") }, diff --git a/back/src/Kyoo.Authentication/Views/AuthApi.cs b/back/src/Kyoo.Authentication/Views/AuthApi.cs index 3a97190b..a3b8135c 100644 --- a/back/src/Kyoo.Authentication/Views/AuthApi.cs +++ b/back/src/Kyoo.Authentication/Views/AuthApi.cs @@ -228,7 +228,7 @@ namespace Kyoo.Authentication.Views return Unauthorized(new RequestError("User not authenticated or token invalid.")); try { - user.ID = userID; + user.Id = userID; return await _users.Edit(user, true); } catch (ItemNotFoundException) @@ -258,7 +258,7 @@ namespace Kyoo.Authentication.Views return Unauthorized(new RequestError("User not authenticated or token invalid.")); try { - user.ID = userID; + user.Id = userID; return await _users.Edit(user, false); } catch (ItemNotFoundException) diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs index 6ce6857e..d2c4d172 100644 --- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -252,73 +252,73 @@ namespace Kyoo.Core.Controllers return (obj, member: memberName) switch { (Collection c, nameof(Collection.Shows)) => ShowRepository - .GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) + .GetAll(x => x.Collections.Any(y => y.Id == obj.Id)) .Then(x => c.Shows = x), (Show s, nameof(Show.People)) => PeopleRepository - .GetFromShow(obj.ID) + .GetFromShow(obj.Id) .Then(x => s.People = x), (Show s, nameof(Show.Seasons)) => _SetRelation(s, - SeasonRepository.GetAll(x => x.Show.ID == obj.ID), + SeasonRepository.GetAll(x => x.Show.Id == obj.Id), (x, y) => x.Seasons = y, - (x, y) => { x.Show = y; x.ShowID = y.ID; }), + (x, y) => { x.Show = y; x.ShowID = y.Id; }), (Show s, nameof(Show.Episodes)) => _SetRelation(s, - EpisodeRepository.GetAll(x => x.Show.ID == obj.ID), + EpisodeRepository.GetAll(x => x.Show.Id == obj.Id), (x, y) => x.Episodes = y, - (x, y) => { x.Show = y; x.ShowID = y.ID; }), + (x, y) => { x.Show = y; x.ShowID = y.Id; }), (Show s, nameof(Show.Collections)) => CollectionRepository - .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) + .GetAll(x => x.Shows.Any(y => y.Id == obj.Id)) .Then(x => s.Collections = x), (Show s, nameof(Show.Studio)) => StudioRepository - .GetOrDefault(x => x.Shows.Any(y => y.ID == obj.ID)) + .GetOrDefault(x => x.Shows.Any(y => y.Id == obj.Id)) .Then(x => { s.Studio = x; - s.StudioID = x?.ID ?? 0; + s.StudioID = x?.Id ?? 0; }), (Season s, nameof(Season.Episodes)) => _SetRelation(s, - EpisodeRepository.GetAll(x => x.Season.ID == obj.ID), + EpisodeRepository.GetAll(x => x.Season.Id == obj.Id), (x, y) => x.Episodes = y, - (x, y) => { x.Season = y; x.SeasonID = y.ID; }), + (x, y) => { x.Season = y; x.SeasonID = y.Id; }), (Season s, nameof(Season.Show)) => ShowRepository - .GetOrDefault(x => x.Seasons.Any(y => y.ID == obj.ID)) + .GetOrDefault(x => x.Seasons.Any(y => y.Id == obj.Id)) .Then(x => { s.Show = x; - s.ShowID = x?.ID ?? 0; + s.ShowID = x?.Id ?? 0; }), (Episode e, nameof(Episode.Show)) => ShowRepository - .GetOrDefault(x => x.Episodes.Any(y => y.ID == obj.ID)) + .GetOrDefault(x => x.Episodes.Any(y => y.Id == obj.Id)) .Then(x => { e.Show = x; - e.ShowID = x?.ID ?? 0; + e.ShowID = x?.Id ?? 0; }), (Episode e, nameof(Episode.Season)) => SeasonRepository - .GetOrDefault(x => x.Episodes.Any(y => y.ID == e.ID)) + .GetOrDefault(x => x.Episodes.Any(y => y.Id == e.Id)) .Then(x => { e.Season = x; - e.SeasonID = x?.ID ?? 0; + e.SeasonID = x?.Id ?? 0; }), (Studio s, nameof(Studio.Shows)) => ShowRepository - .GetAll(x => x.Studio.ID == obj.ID) + .GetAll(x => x.Studio.Id == obj.Id) .Then(x => s.Shows = x), (People p, nameof(People.Roles)) => PeopleRepository - .GetFromPeople(obj.ID) + .GetFromPeople(obj.Id) .Then(x => p.Roles = x), _ => throw new ArgumentException($"Couldn't find a way to load {memberName} of {obj.Slug}.") diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index 489865ac..fe4e7412 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -64,7 +64,7 @@ namespace Kyoo.Core.Controllers // Edit episode slugs when the show's slug changes. shows.OnEdited += (show) => { - List episodes = _database.Episodes.AsTracking().Where(x => x.ShowID == show.ID).ToList(); + List episodes = _database.Episodes.AsTracking().Where(x => x.ShowID == show.Id).ToList(); foreach (Episode ep in episodes) { ep.ShowSlug = show.Slug; @@ -142,7 +142,7 @@ namespace Kyoo.Core.Controllers public override async Task Create(Episode obj) { await base.Create(obj); - obj.ShowSlug = obj.Show?.Slug ?? _database.Shows.First(x => x.ID == obj.ShowID).Slug; + obj.ShowSlug = obj.Show?.Slug ?? _database.Shows.First(x => x.Id == obj.ShowID).Slug; _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => obj.SeasonNumber != null && obj.EpisodeNumber != null @@ -163,7 +163,7 @@ namespace Kyoo.Core.Controllers throw new ArgumentException($"Can't store an episode not related " + $"to any show (showID: {resource.ShowID})."); } - resource.ShowID = resource.Show.ID; + resource.ShowID = resource.Show.Id; } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index d8325a51..593407aa 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -54,7 +54,7 @@ namespace Kyoo.Core.Controllers /// public override async Task GetOrDefault(int id) { - return (await _database.LibraryItems.FirstOrDefaultAsync(x => x.ID == id)).ToItem(); + return (await _database.LibraryItems.FirstOrDefaultAsync(x => x.Id == id)).ToItem(); } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index f2b4cf56..6d33283e 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -112,7 +112,7 @@ namespace Kyoo.Core.Controllers throw new SwitchExpressionException(); } } - return _Sort(query, sortBy, false).ThenBy(x => x.ID); + return _Sort(query, sortBy, false).ThenBy(x => x.Id); } private static Func _GetComparisonExpression( @@ -167,7 +167,7 @@ namespace Kyoo.Core.Controllers } // Don't forget that every sorts must end with a ID sort (to differenciate equalities). - Sort.By id = new(x => x.ID); + Sort.By id = new(x => x.Id); IEnumerable.By> sorts = _GetSortsBy(sort).Append(id); BinaryExpression filter = null; @@ -234,7 +234,7 @@ namespace Kyoo.Core.Controllers /// The tracked resource with the given ID protected virtual async Task GetWithTracking(int id) { - T ret = await Database.Set().AsTracking().FirstOrDefaultAsync(x => x.ID == id); + T ret = await Database.Set().AsTracking().FirstOrDefaultAsync(x => x.Id == id); if (ret == null) throw new ItemNotFoundException($"No {typeof(T).Name} found with the id {id}"); return ret; @@ -270,7 +270,7 @@ namespace Kyoo.Core.Controllers /// public virtual Task GetOrDefault(int id) { - return Database.Set().FirstOrDefaultAsync(x => x.ID == id); + return Database.Set().FirstOrDefaultAsync(x => x.Id == id); } /// @@ -394,7 +394,7 @@ namespace Kyoo.Core.Controllers Database.ChangeTracker.LazyLoadingEnabled = false; try { - T old = await GetWithTracking(edited.ID); + T old = await GetWithTracking(edited.Id); if (resetOld) old = Merger.Nullify(old); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs index 1b6fe33a..49290f31 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs @@ -95,7 +95,7 @@ namespace Kyoo.Core.Controllers if (resource.Studio != null) { resource.Studio = await _studios.CreateIfNotExists(resource.Studio); - resource.StudioID = resource.Studio.ID; + resource.StudioID = resource.Studio.Id; } if (resource.People != null) @@ -104,7 +104,7 @@ namespace Kyoo.Core.Controllers { role.People = _database.LocalEntity(role.People.Slug) ?? await _people.CreateIfNotExists(role.People); - role.PeopleID = role.People.ID; + role.PeopleID = role.People.Id; _database.Entry(role).State = EntityState.Added; } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs index 766629e0..ee52b798 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs @@ -92,7 +92,7 @@ namespace Kyoo.Core.Controllers { role.Show = _database.LocalEntity(role.Show.Slug) ?? await _shows.Value.CreateIfNotExists(role.Show); - role.ShowID = role.Show.ID; + role.ShowID = role.Show.Id; _database.Entry(role).State = EntityState.Added; } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 104b2367..37a2e820 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -55,7 +55,7 @@ namespace Kyoo.Core.Controllers // Edit seasons slugs when the show's slug changes. shows.OnEdited += (show) => { - List seasons = _database.Seasons.AsTracking().Where(x => x.ShowID == show.ID).ToList(); + List seasons = _database.Seasons.AsTracking().Where(x => x.ShowID == show.Id).ToList(); foreach (Season season in seasons) { season.ShowSlug = show.Slug; @@ -112,7 +112,7 @@ namespace Kyoo.Core.Controllers public override async Task Create(Season obj) { await base.Create(obj); - obj.ShowSlug = _database.Shows.First(x => x.ID == obj.ShowID).Slug; + obj.ShowSlug = _database.Shows.First(x => x.Id == obj.ShowID).Slug; _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => Get(obj.ShowID, obj.SeasonNumber)); OnResourceCreated(obj); @@ -130,7 +130,7 @@ namespace Kyoo.Core.Controllers throw new ArgumentException($"Can't store a season not related to any show " + $"(showID: {resource.ShowID})."); } - resource.ShowID = resource.Show.ID; + resource.ShowID = resource.Show.Id; } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index 32e378ff..2bfb8fda 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -97,7 +97,7 @@ namespace Kyoo.Core.Controllers if (resource.Studio != null) { resource.Studio = await _studios.CreateIfNotExists(resource.Studio); - resource.StudioID = resource.Studio.ID; + resource.StudioID = resource.Studio.Id; } if (resource.People != null) @@ -106,7 +106,7 @@ namespace Kyoo.Core.Controllers { role.People = _database.LocalEntity(role.People.Slug) ?? await _people.CreateIfNotExists(role.People); - role.PeopleID = role.People.ID; + role.PeopleID = role.People.Id; _database.Entry(role).State = EntityState.Added; } } @@ -133,7 +133,7 @@ namespace Kyoo.Core.Controllers /// public Task GetSlug(int showID) { - return _database.Shows.Where(x => x.ID == showID) + return _database.Shows.Where(x => x.Id == showID) .Select(x => x.Slug) .FirstOrDefaultAsync(); } diff --git a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs index cb941c43..2a786bd8 100644 --- a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs +++ b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs @@ -161,11 +161,11 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Edit([FromBody] T resource) { - if (resource.ID > 0) + if (resource.Id > 0) return await Repository.Edit(resource, true); T old = await Repository.Get(resource.Slug); - resource.ID = old.ID; + resource.Id = old.Id; return await Repository.Edit(resource, true); } @@ -187,11 +187,11 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Patch([FromBody] T resource) { - if (resource.ID > 0) + if (resource.Id > 0) return await Repository.Edit(resource, false); T old = await Repository.Get(resource.Slug); - resource.ID = old.ID; + resource.Id = old.Id; return await Repository.Edit(resource, false); } diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index bef2dd16..a5d1828a 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -95,7 +95,7 @@ namespace Kyoo.Postgresql public IQueryable LibraryItems => Shows.Select(x => new BagItem { - ID = x.ID, + Id = x.Id, Slug = x.Slug, Name = x.Name, AirDate = x.StartAir, @@ -103,7 +103,7 @@ namespace Kyoo.Postgresql Rest = x }).Union(Movies.Select(x => new BagItem { - ID = x.ID, + Id = x.Id, Slug = x.Slug, Name = x.Name, AirDate = x.AirDate, @@ -111,7 +111,7 @@ namespace Kyoo.Postgresql Rest = x })).Union(Collections.Select(x => new BagItem { - ID = x.ID, + Id = x.Id, Slug = x.Slug, Name = x.Name, AirDate = null, @@ -199,7 +199,7 @@ namespace Kyoo.Postgresql .Property(x => x.ExternalId) .HasConversion( v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null), - v => JsonSerializer.Deserialize>(v, (JsonSerializerOptions)null) + v => JsonSerializer.Deserialize>(v, (JsonSerializerOptions)null) ) .HasColumnType("json"); } @@ -350,7 +350,7 @@ namespace Kyoo.Postgresql public T GetTemporaryObject(T model) where T : class, IResource { - T tmp = Set().Local.FirstOrDefault(x => x.ID == model.ID); + T tmp = Set().Local.FirstOrDefault(x => x.Id == model.Id); if (tmp != null) return tmp; Entry(model).State = EntityState.Unchanged; diff --git a/back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.Designer.cs similarity index 90% rename from back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.Designer.cs rename to back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.Designer.cs index 98114327..8fa8ae08 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.Designer.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.Designer.cs @@ -13,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20230805051120_initial")] + [Migration("20230805052627_initial")] partial class initial { /// @@ -30,18 +30,18 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("CollectionMovie", b => { - b.Property("CollectionsID") + b.Property("CollectionsId") .HasColumnType("integer") .HasColumnName("collections_id"); - b.Property("MoviesID") + b.Property("MoviesId") .HasColumnType("integer") .HasColumnName("movies_id"); - b.HasKey("CollectionsID", "MoviesID") + b.HasKey("CollectionsId", "MoviesId") .HasName("pk_collection_movie"); - b.HasIndex("MoviesID") + b.HasIndex("MoviesId") .HasDatabaseName("ix_collection_movie_movies_id"); b.ToTable("collection_movie", (string)null); @@ -49,12 +49,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ExternalId") .IsRequired() @@ -76,7 +76,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_collections"); b.HasIndex("Slug") @@ -88,12 +88,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("AbsoluteNumber") .HasColumnType("integer") @@ -143,7 +143,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_episodes"); b.HasIndex("SeasonID") @@ -162,12 +162,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("AirDate") .HasColumnType("timestamp with time zone") @@ -229,7 +229,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("trailer"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_movies"); b.HasIndex("Slug") @@ -244,12 +244,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ExternalId") .IsRequired() @@ -267,7 +267,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_people"); b.HasIndex("Slug") @@ -279,12 +279,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("MovieID") .HasColumnType("integer") @@ -308,7 +308,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("type"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_people_roles"); b.HasIndex("MovieID") @@ -325,12 +325,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => { - b.Property("ID") + 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") @@ -367,7 +367,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("start_date"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_seasons"); b.HasIndex("Slug") @@ -383,12 +383,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Aliases") .IsRequired() @@ -449,7 +449,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("trailer"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_shows"); b.HasIndex("Slug") @@ -464,12 +464,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ExternalId") .IsRequired() @@ -487,7 +487,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_studios"); b.HasIndex("Slug") @@ -499,12 +499,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Email") .IsRequired() @@ -532,7 +532,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("username"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_users"); b.HasIndex("Slug") @@ -567,18 +567,18 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("ShowUser", b => { - b.Property("UsersID") + b.Property("UsersId") .HasColumnType("integer") .HasColumnName("users_id"); - b.Property("WatchedID") + b.Property("WatchedId") .HasColumnType("integer") .HasColumnName("watched_id"); - b.HasKey("UsersID", "WatchedID") + b.HasKey("UsersId", "WatchedId") .HasName("pk_link_user_show"); - b.HasIndex("WatchedID") + b.HasIndex("WatchedId") .HasDatabaseName("ix_link_user_show_watched_id"); b.ToTable("link_user_show", (string)null); @@ -607,14 +607,14 @@ namespace Kyoo.Postgresql.Migrations { b.HasOne("Kyoo.Abstractions.Models.Collection", null) .WithMany() - .HasForeignKey("CollectionsID") + .HasForeignKey("CollectionsId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() .HasConstraintName("fk_collection_movie_collections_collections_id"); b.HasOne("Kyoo.Abstractions.Models.Movie", null) .WithMany() - .HasForeignKey("MoviesID") + .HasForeignKey("MoviesId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() .HasConstraintName("fk_collection_movie_movies_movies_id"); @@ -624,7 +624,7 @@ namespace Kyoo.Postgresql.Migrations { b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("CollectionID") + b1.Property("CollectionId") .HasColumnType("integer") .HasColumnName("id"); @@ -639,18 +639,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("CollectionID"); + b1.HasKey("CollectionId"); b1.ToTable("collections"); b1.WithOwner() - .HasForeignKey("CollectionID") + .HasForeignKey("CollectionId") .HasConstraintName("fk_collections_collections_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("CollectionID") + b1.Property("CollectionId") .HasColumnType("integer") .HasColumnName("id"); @@ -665,18 +665,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("CollectionID"); + b1.HasKey("CollectionId"); b1.ToTable("collections"); b1.WithOwner() - .HasForeignKey("CollectionID") + .HasForeignKey("CollectionId") .HasConstraintName("fk_collections_collections_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("CollectionID") + b1.Property("CollectionId") .HasColumnType("integer") .HasColumnName("id"); @@ -691,12 +691,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("CollectionID"); + b1.HasKey("CollectionId"); b1.ToTable("collections"); b1.WithOwner() - .HasForeignKey("CollectionID") + .HasForeignKey("CollectionId") .HasConstraintName("fk_collections_collections_id"); }); @@ -724,7 +724,7 @@ namespace Kyoo.Postgresql.Migrations b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("EpisodeID") + b1.Property("EpisodeId") .HasColumnType("integer") .HasColumnName("id"); @@ -739,18 +739,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("EpisodeID"); + b1.HasKey("EpisodeId"); b1.ToTable("episodes"); b1.WithOwner() - .HasForeignKey("EpisodeID") + .HasForeignKey("EpisodeId") .HasConstraintName("fk_episodes_episodes_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("EpisodeID") + b1.Property("EpisodeId") .HasColumnType("integer") .HasColumnName("id"); @@ -765,18 +765,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("EpisodeID"); + b1.HasKey("EpisodeId"); b1.ToTable("episodes"); b1.WithOwner() - .HasForeignKey("EpisodeID") + .HasForeignKey("EpisodeId") .HasConstraintName("fk_episodes_episodes_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("EpisodeID") + b1.Property("EpisodeId") .HasColumnType("integer") .HasColumnName("id"); @@ -791,12 +791,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("EpisodeID"); + b1.HasKey("EpisodeId"); b1.ToTable("episodes"); b1.WithOwner() - .HasForeignKey("EpisodeID") + .HasForeignKey("EpisodeId") .HasConstraintName("fk_episodes_episodes_id"); }); @@ -821,7 +821,7 @@ namespace Kyoo.Postgresql.Migrations b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("MovieID") + b1.Property("MovieId") .HasColumnType("integer") .HasColumnName("id"); @@ -836,18 +836,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("MovieID"); + b1.HasKey("MovieId"); b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("MovieID") + .HasForeignKey("MovieId") .HasConstraintName("fk_movies_movies_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("MovieID") + b1.Property("MovieId") .HasColumnType("integer") .HasColumnName("id"); @@ -862,18 +862,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("MovieID"); + b1.HasKey("MovieId"); b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("MovieID") + .HasForeignKey("MovieId") .HasConstraintName("fk_movies_movies_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("MovieID") + b1.Property("MovieId") .HasColumnType("integer") .HasColumnName("id"); @@ -888,12 +888,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("MovieID"); + b1.HasKey("MovieId"); b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("MovieID") + .HasForeignKey("MovieId") .HasConstraintName("fk_movies_movies_id"); }); @@ -910,7 +910,7 @@ namespace Kyoo.Postgresql.Migrations { b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("PeopleID") + b1.Property("PeopleId") .HasColumnType("integer") .HasColumnName("id"); @@ -925,18 +925,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("PeopleID"); + b1.HasKey("PeopleId"); b1.ToTable("people"); b1.WithOwner() - .HasForeignKey("PeopleID") + .HasForeignKey("PeopleId") .HasConstraintName("fk_people_people_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("PeopleID") + b1.Property("PeopleId") .HasColumnType("integer") .HasColumnName("id"); @@ -951,18 +951,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("PeopleID"); + b1.HasKey("PeopleId"); b1.ToTable("people"); b1.WithOwner() - .HasForeignKey("PeopleID") + .HasForeignKey("PeopleId") .HasConstraintName("fk_people_people_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("PeopleID") + b1.Property("PeopleId") .HasColumnType("integer") .HasColumnName("id"); @@ -977,12 +977,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("PeopleID"); + b1.HasKey("PeopleId"); b1.ToTable("people"); b1.WithOwner() - .HasForeignKey("PeopleID") + .HasForeignKey("PeopleId") .HasConstraintName("fk_people_people_id"); }); @@ -1030,7 +1030,7 @@ namespace Kyoo.Postgresql.Migrations b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("SeasonID") + b1.Property("SeasonId") .HasColumnType("integer") .HasColumnName("id"); @@ -1045,18 +1045,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("SeasonID"); + b1.HasKey("SeasonId"); b1.ToTable("seasons"); b1.WithOwner() - .HasForeignKey("SeasonID") + .HasForeignKey("SeasonId") .HasConstraintName("fk_seasons_seasons_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("SeasonID") + b1.Property("SeasonId") .HasColumnType("integer") .HasColumnName("id"); @@ -1071,18 +1071,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("SeasonID"); + b1.HasKey("SeasonId"); b1.ToTable("seasons"); b1.WithOwner() - .HasForeignKey("SeasonID") + .HasForeignKey("SeasonId") .HasConstraintName("fk_seasons_seasons_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("SeasonID") + b1.Property("SeasonId") .HasColumnType("integer") .HasColumnName("id"); @@ -1097,12 +1097,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("SeasonID"); + b1.HasKey("SeasonId"); b1.ToTable("seasons"); b1.WithOwner() - .HasForeignKey("SeasonID") + .HasForeignKey("SeasonId") .HasConstraintName("fk_seasons_seasons_id"); }); @@ -1125,7 +1125,7 @@ namespace Kyoo.Postgresql.Migrations b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("ShowID") + b1.Property("ShowId") .HasColumnType("integer") .HasColumnName("id"); @@ -1140,18 +1140,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("ShowID"); + b1.HasKey("ShowId"); b1.ToTable("shows"); b1.WithOwner() - .HasForeignKey("ShowID") + .HasForeignKey("ShowId") .HasConstraintName("fk_shows_shows_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("ShowID") + b1.Property("ShowId") .HasColumnType("integer") .HasColumnName("id"); @@ -1166,18 +1166,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("ShowID"); + b1.HasKey("ShowId"); b1.ToTable("shows"); b1.WithOwner() - .HasForeignKey("ShowID") + .HasForeignKey("ShowId") .HasConstraintName("fk_shows_shows_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("ShowID") + b1.Property("ShowId") .HasColumnType("integer") .HasColumnName("id"); @@ -1192,12 +1192,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("ShowID"); + b1.HasKey("ShowId"); b1.ToTable("shows"); b1.WithOwner() - .HasForeignKey("ShowID") + .HasForeignKey("ShowId") .HasConstraintName("fk_shows_shows_id"); }); @@ -1214,7 +1214,7 @@ namespace Kyoo.Postgresql.Migrations { b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("UserID") + b1.Property("UserId") .HasColumnType("integer") .HasColumnName("id"); @@ -1229,12 +1229,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("UserID"); + b1.HasKey("UserId"); b1.ToTable("users"); b1.WithOwner() - .HasForeignKey("UserID") + .HasForeignKey("UserId") .HasConstraintName("fk_users_users_id"); }); @@ -1264,14 +1264,14 @@ namespace Kyoo.Postgresql.Migrations { b.HasOne("Kyoo.Abstractions.Models.User", null) .WithMany() - .HasForeignKey("UsersID") + .HasForeignKey("UsersId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() .HasConstraintName("fk_link_user_show_users_users_id"); b.HasOne("Kyoo.Abstractions.Models.Show", null) .WithMany() - .HasForeignKey("WatchedID") + .HasForeignKey("WatchedId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() .HasConstraintName("fk_link_user_show_shows_watched_id"); diff --git a/back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.cs b/back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.cs similarity index 100% rename from back/src/Kyoo.Postgresql/Migrations/20230805051120_initial.cs rename to back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.cs diff --git a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index 93540795..d41b691f 100644 --- a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -27,18 +27,18 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("CollectionMovie", b => { - b.Property("CollectionsID") + b.Property("CollectionsId") .HasColumnType("integer") .HasColumnName("collections_id"); - b.Property("MoviesID") + b.Property("MoviesId") .HasColumnType("integer") .HasColumnName("movies_id"); - b.HasKey("CollectionsID", "MoviesID") + b.HasKey("CollectionsId", "MoviesId") .HasName("pk_collection_movie"); - b.HasIndex("MoviesID") + b.HasIndex("MoviesId") .HasDatabaseName("ix_collection_movie_movies_id"); b.ToTable("collection_movie", (string)null); @@ -46,12 +46,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ExternalId") .IsRequired() @@ -73,7 +73,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_collections"); b.HasIndex("Slug") @@ -85,12 +85,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("AbsoluteNumber") .HasColumnType("integer") @@ -140,7 +140,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_episodes"); b.HasIndex("SeasonID") @@ -159,12 +159,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("AirDate") .HasColumnType("timestamp with time zone") @@ -226,7 +226,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("trailer"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_movies"); b.HasIndex("Slug") @@ -241,12 +241,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ExternalId") .IsRequired() @@ -264,7 +264,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_people"); b.HasIndex("Slug") @@ -276,12 +276,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("MovieID") .HasColumnType("integer") @@ -305,7 +305,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("type"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_people_roles"); b.HasIndex("MovieID") @@ -322,12 +322,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => { - b.Property("ID") + 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") @@ -364,7 +364,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("start_date"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_seasons"); b.HasIndex("Slug") @@ -380,12 +380,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Aliases") .IsRequired() @@ -446,7 +446,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("trailer"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_shows"); b.HasIndex("Slug") @@ -461,12 +461,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ExternalId") .IsRequired() @@ -484,7 +484,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_studios"); b.HasIndex("Slug") @@ -496,12 +496,12 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasColumnName("id"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ID")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Email") .IsRequired() @@ -529,7 +529,7 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("username"); - b.HasKey("ID") + b.HasKey("Id") .HasName("pk_users"); b.HasIndex("Slug") @@ -564,18 +564,18 @@ namespace Kyoo.Postgresql.Migrations modelBuilder.Entity("ShowUser", b => { - b.Property("UsersID") + b.Property("UsersId") .HasColumnType("integer") .HasColumnName("users_id"); - b.Property("WatchedID") + b.Property("WatchedId") .HasColumnType("integer") .HasColumnName("watched_id"); - b.HasKey("UsersID", "WatchedID") + b.HasKey("UsersId", "WatchedId") .HasName("pk_link_user_show"); - b.HasIndex("WatchedID") + b.HasIndex("WatchedId") .HasDatabaseName("ix_link_user_show_watched_id"); b.ToTable("link_user_show", (string)null); @@ -604,14 +604,14 @@ namespace Kyoo.Postgresql.Migrations { b.HasOne("Kyoo.Abstractions.Models.Collection", null) .WithMany() - .HasForeignKey("CollectionsID") + .HasForeignKey("CollectionsId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() .HasConstraintName("fk_collection_movie_collections_collections_id"); b.HasOne("Kyoo.Abstractions.Models.Movie", null) .WithMany() - .HasForeignKey("MoviesID") + .HasForeignKey("MoviesId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() .HasConstraintName("fk_collection_movie_movies_movies_id"); @@ -621,7 +621,7 @@ namespace Kyoo.Postgresql.Migrations { b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("CollectionID") + b1.Property("CollectionId") .HasColumnType("integer") .HasColumnName("id"); @@ -636,18 +636,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("CollectionID"); + b1.HasKey("CollectionId"); b1.ToTable("collections"); b1.WithOwner() - .HasForeignKey("CollectionID") + .HasForeignKey("CollectionId") .HasConstraintName("fk_collections_collections_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("CollectionID") + b1.Property("CollectionId") .HasColumnType("integer") .HasColumnName("id"); @@ -662,18 +662,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("CollectionID"); + b1.HasKey("CollectionId"); b1.ToTable("collections"); b1.WithOwner() - .HasForeignKey("CollectionID") + .HasForeignKey("CollectionId") .HasConstraintName("fk_collections_collections_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("CollectionID") + b1.Property("CollectionId") .HasColumnType("integer") .HasColumnName("id"); @@ -688,12 +688,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("CollectionID"); + b1.HasKey("CollectionId"); b1.ToTable("collections"); b1.WithOwner() - .HasForeignKey("CollectionID") + .HasForeignKey("CollectionId") .HasConstraintName("fk_collections_collections_id"); }); @@ -721,7 +721,7 @@ namespace Kyoo.Postgresql.Migrations b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("EpisodeID") + b1.Property("EpisodeId") .HasColumnType("integer") .HasColumnName("id"); @@ -736,18 +736,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("EpisodeID"); + b1.HasKey("EpisodeId"); b1.ToTable("episodes"); b1.WithOwner() - .HasForeignKey("EpisodeID") + .HasForeignKey("EpisodeId") .HasConstraintName("fk_episodes_episodes_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("EpisodeID") + b1.Property("EpisodeId") .HasColumnType("integer") .HasColumnName("id"); @@ -762,18 +762,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("EpisodeID"); + b1.HasKey("EpisodeId"); b1.ToTable("episodes"); b1.WithOwner() - .HasForeignKey("EpisodeID") + .HasForeignKey("EpisodeId") .HasConstraintName("fk_episodes_episodes_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("EpisodeID") + b1.Property("EpisodeId") .HasColumnType("integer") .HasColumnName("id"); @@ -788,12 +788,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("EpisodeID"); + b1.HasKey("EpisodeId"); b1.ToTable("episodes"); b1.WithOwner() - .HasForeignKey("EpisodeID") + .HasForeignKey("EpisodeId") .HasConstraintName("fk_episodes_episodes_id"); }); @@ -818,7 +818,7 @@ namespace Kyoo.Postgresql.Migrations b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("MovieID") + b1.Property("MovieId") .HasColumnType("integer") .HasColumnName("id"); @@ -833,18 +833,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("MovieID"); + b1.HasKey("MovieId"); b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("MovieID") + .HasForeignKey("MovieId") .HasConstraintName("fk_movies_movies_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("MovieID") + b1.Property("MovieId") .HasColumnType("integer") .HasColumnName("id"); @@ -859,18 +859,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("MovieID"); + b1.HasKey("MovieId"); b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("MovieID") + .HasForeignKey("MovieId") .HasConstraintName("fk_movies_movies_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("MovieID") + b1.Property("MovieId") .HasColumnType("integer") .HasColumnName("id"); @@ -885,12 +885,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("MovieID"); + b1.HasKey("MovieId"); b1.ToTable("movies"); b1.WithOwner() - .HasForeignKey("MovieID") + .HasForeignKey("MovieId") .HasConstraintName("fk_movies_movies_id"); }); @@ -907,7 +907,7 @@ namespace Kyoo.Postgresql.Migrations { b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("PeopleID") + b1.Property("PeopleId") .HasColumnType("integer") .HasColumnName("id"); @@ -922,18 +922,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("PeopleID"); + b1.HasKey("PeopleId"); b1.ToTable("people"); b1.WithOwner() - .HasForeignKey("PeopleID") + .HasForeignKey("PeopleId") .HasConstraintName("fk_people_people_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("PeopleID") + b1.Property("PeopleId") .HasColumnType("integer") .HasColumnName("id"); @@ -948,18 +948,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("PeopleID"); + b1.HasKey("PeopleId"); b1.ToTable("people"); b1.WithOwner() - .HasForeignKey("PeopleID") + .HasForeignKey("PeopleId") .HasConstraintName("fk_people_people_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("PeopleID") + b1.Property("PeopleId") .HasColumnType("integer") .HasColumnName("id"); @@ -974,12 +974,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("PeopleID"); + b1.HasKey("PeopleId"); b1.ToTable("people"); b1.WithOwner() - .HasForeignKey("PeopleID") + .HasForeignKey("PeopleId") .HasConstraintName("fk_people_people_id"); }); @@ -1027,7 +1027,7 @@ namespace Kyoo.Postgresql.Migrations b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("SeasonID") + b1.Property("SeasonId") .HasColumnType("integer") .HasColumnName("id"); @@ -1042,18 +1042,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("SeasonID"); + b1.HasKey("SeasonId"); b1.ToTable("seasons"); b1.WithOwner() - .HasForeignKey("SeasonID") + .HasForeignKey("SeasonId") .HasConstraintName("fk_seasons_seasons_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("SeasonID") + b1.Property("SeasonId") .HasColumnType("integer") .HasColumnName("id"); @@ -1068,18 +1068,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("SeasonID"); + b1.HasKey("SeasonId"); b1.ToTable("seasons"); b1.WithOwner() - .HasForeignKey("SeasonID") + .HasForeignKey("SeasonId") .HasConstraintName("fk_seasons_seasons_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("SeasonID") + b1.Property("SeasonId") .HasColumnType("integer") .HasColumnName("id"); @@ -1094,12 +1094,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("SeasonID"); + b1.HasKey("SeasonId"); b1.ToTable("seasons"); b1.WithOwner() - .HasForeignKey("SeasonID") + .HasForeignKey("SeasonId") .HasConstraintName("fk_seasons_seasons_id"); }); @@ -1122,7 +1122,7 @@ namespace Kyoo.Postgresql.Migrations b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("ShowID") + b1.Property("ShowId") .HasColumnType("integer") .HasColumnName("id"); @@ -1137,18 +1137,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("ShowID"); + b1.HasKey("ShowId"); b1.ToTable("shows"); b1.WithOwner() - .HasForeignKey("ShowID") + .HasForeignKey("ShowId") .HasConstraintName("fk_shows_shows_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => { - b1.Property("ShowID") + b1.Property("ShowId") .HasColumnType("integer") .HasColumnName("id"); @@ -1163,18 +1163,18 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("poster_source"); - b1.HasKey("ShowID"); + b1.HasKey("ShowId"); b1.ToTable("shows"); b1.WithOwner() - .HasForeignKey("ShowID") + .HasForeignKey("ShowId") .HasConstraintName("fk_shows_shows_id"); }); b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => { - b1.Property("ShowID") + b1.Property("ShowId") .HasColumnType("integer") .HasColumnName("id"); @@ -1189,12 +1189,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("thumbnail_source"); - b1.HasKey("ShowID"); + b1.HasKey("ShowId"); b1.ToTable("shows"); b1.WithOwner() - .HasForeignKey("ShowID") + .HasForeignKey("ShowId") .HasConstraintName("fk_shows_shows_id"); }); @@ -1211,7 +1211,7 @@ namespace Kyoo.Postgresql.Migrations { b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => { - b1.Property("UserID") + b1.Property("UserId") .HasColumnType("integer") .HasColumnName("id"); @@ -1226,12 +1226,12 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("logo_source"); - b1.HasKey("UserID"); + b1.HasKey("UserId"); b1.ToTable("users"); b1.WithOwner() - .HasForeignKey("UserID") + .HasForeignKey("UserId") .HasConstraintName("fk_users_users_id"); }); @@ -1261,14 +1261,14 @@ namespace Kyoo.Postgresql.Migrations { b.HasOne("Kyoo.Abstractions.Models.User", null) .WithMany() - .HasForeignKey("UsersID") + .HasForeignKey("UsersId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() .HasConstraintName("fk_link_user_show_users_users_id"); b.HasOne("Kyoo.Abstractions.Models.Show", null) .WithMany() - .HasForeignKey("WatchedID") + .HasForeignKey("WatchedId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() .HasConstraintName("fk_link_user_show_shows_watched_id"); diff --git a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs index fd21f8ba..f6aa7de4 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs @@ -63,7 +63,7 @@ namespace Kyoo.Tests.Database [Fact] public async Task GetByIdTest() { - T value = await _repository.Get(TestSample.Get().ID); + T value = await _repository.Get(TestSample.Get().Id); KAssert.DeepEqual(TestSample.Get(), value); } @@ -89,7 +89,7 @@ namespace Kyoo.Tests.Database [Fact] public async Task DeleteByIdTest() { - await _repository.Delete(TestSample.Get().ID); + await _repository.Delete(TestSample.Get().Id); Assert.Equal(0, await _repository.GetCount()); } @@ -114,7 +114,7 @@ namespace Kyoo.Tests.Database await _repository.Delete(TestSample.Get()); T expected = TestSample.Get(); - expected.ID = 0; + expected.Id = 0; await _repository.Create(expected); KAssert.DeepEqual(expected, await _repository.Get(expected.Slug)); } @@ -149,13 +149,13 @@ namespace Kyoo.Tests.Database [Fact] public async Task EditNonExistingTest() { - await Assert.ThrowsAsync(() => _repository.Edit(new T { ID = 56 }, false)); + await Assert.ThrowsAsync(() => _repository.Edit(new T { Id = 56 }, false)); } [Fact] public async Task GetExpressionIDTest() { - KAssert.DeepEqual(TestSample.Get(), await _repository.Get(x => x.ID == TestSample.Get().ID)); + KAssert.DeepEqual(TestSample.Get(), await _repository.Get(x => x.Id == TestSample.Get().Id)); } [Fact] diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs index 8ecb9bae..ef38efb1 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -80,17 +80,17 @@ namespace Kyoo.Tests.Database Collection collection = TestSample.GetNew(); collection.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, - new MetadataID + new MetadataId { Provider = TestSample.GetNew(), Link = "new-provider-link", - DataID = "new-id" + DataId = "new-id" } }; await _repository.Create(collection); @@ -125,11 +125,11 @@ namespace Kyoo.Tests.Database Collection value = await _repository.Get(TestSample.Get().Slug); value.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, }; await _repository.Edit(value, false); @@ -147,13 +147,13 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Collection value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new List { new() { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, }; await _repository.Edit(value, false); @@ -168,11 +168,11 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataID + value.ExternalId.Add(new MetadataId { Provider = TestSample.GetNew(), Link = "link", - DataID = "id" + DataId = "id" }); await _repository.Edit(value, false); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index 6e3a6b18..8ee85408 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -57,7 +57,7 @@ namespace Kyoo.Tests.Database Assert.Equal($"{TestSample.Get().Slug}-s1e1", episode.Slug); Show show = new() { - ID = episode.ShowID, + Id = episode.ShowID, Slug = "new-slug" }; await Repositories.LibraryManager.ShowRepository.Edit(show, false); @@ -72,7 +72,7 @@ namespace Kyoo.Tests.Database Assert.Equal($"{TestSample.Get().Slug}-s1e1", episode.Slug); episode = await _repository.Edit(new Episode { - ID = 1, + Id = 1, SeasonNumber = 2, ShowID = 1 }, false); @@ -88,7 +88,7 @@ namespace Kyoo.Tests.Database Assert.Equal($"{TestSample.Get().Slug}-s1e1", episode.Slug); episode = await _repository.Edit(new Episode { - ID = 1, + Id = 1, EpisodeNumber = 2, ShowID = 1 }, false); @@ -102,7 +102,7 @@ namespace Kyoo.Tests.Database { Episode episode = await _repository.Create(new Episode { - ShowID = TestSample.Get().ID, + ShowID = TestSample.Get().Id, SeasonNumber = 2, EpisodeNumber = 4 }); @@ -129,7 +129,7 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode()); Show show = new() { - ID = episode.ShowID, + Id = episode.ShowID, Slug = "new-slug" }; await Repositories.LibraryManager.ShowRepository.Edit(show, false); @@ -143,7 +143,7 @@ namespace Kyoo.Tests.Database await _repository.Create(TestSample.GetAbsoluteEpisode()); Episode episode = await _repository.Edit(new Episode { - ID = 2, + Id = 2, AbsoluteNumber = 56, ShowID = 1 }, false); @@ -158,7 +158,7 @@ namespace Kyoo.Tests.Database await _repository.Create(TestSample.GetAbsoluteEpisode()); Episode episode = await _repository.Edit(new Episode { - ID = 2, + Id = 2, SeasonNumber = 1, EpisodeNumber = 2, ShowID = 1 @@ -195,7 +195,7 @@ namespace Kyoo.Tests.Database await _repository.Create(TestSample.GetMovieEpisode()); await Repositories.LibraryManager.Edit(new Show { - ID = 1, + Id = 1, Slug = "john-wick" }, false); Episode episode = await _repository.Get(3); @@ -208,17 +208,17 @@ namespace Kyoo.Tests.Database Episode value = TestSample.GetNew(); value.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, - new MetadataID + new MetadataId { Provider = TestSample.GetNew(), Link = "new-provider-link", - DataID = "new-id" + DataId = "new-id" } }; await _repository.Create(value); @@ -253,11 +253,11 @@ namespace Kyoo.Tests.Database Episode value = await _repository.Get(TestSample.Get().Slug); value.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, }; await _repository.Edit(value, false); @@ -275,13 +275,13 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Episode value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new List { new() { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, }; await _repository.Edit(value, false); @@ -296,11 +296,11 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataID + value.ExternalId.Add(new MetadataId { Provider = TestSample.GetNew(), Link = "link", - DataID = "id" + DataId = "id" }); await _repository.Edit(value, false); @@ -342,9 +342,9 @@ namespace Kyoo.Tests.Database await _repository.Delete(TestSample.Get()); Episode expected = TestSample.Get(); - expected.ID = 0; - expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).ID; - expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).ID; + expected.Id = 0; + expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; + expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; await _repository.Create(expected); KAssert.DeepEqual(expected, await _repository.Get(expected.Slug)); } @@ -355,8 +355,8 @@ namespace Kyoo.Tests.Database Episode expected = TestSample.Get(); KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get())); await _repository.Delete(TestSample.Get()); - expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).ID; - expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).ID; + expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; + expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(expected)); } } diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs index bd6a3e70..e6b58242 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs @@ -54,17 +54,17 @@ namespace Kyoo.Tests.Database People value = TestSample.GetNew(); value.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, - new MetadataID + new MetadataId { Provider = TestSample.GetNew(), Link = "new-provider-link", - DataID = "new-id" + DataId = "new-id" } }; await _repository.Create(value); @@ -99,11 +99,11 @@ namespace Kyoo.Tests.Database People value = await _repository.Get(TestSample.Get().Slug); value.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, }; await _repository.Edit(value, false); @@ -121,13 +121,13 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { People value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new List { new() { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, }; await _repository.Edit(value, false); @@ -142,11 +142,11 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataID + value.ExternalId.Add(new MetadataId { Provider = TestSample.GetNew(), Link = "link", - DataID = "id" + DataId = "id" }); await _repository.Edit(value, false); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index c1107617..886cbade 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -55,7 +55,7 @@ namespace Kyoo.Tests.Database Assert.Equal("anohana-s1", season.Slug); Show show = new() { - ID = season.ShowID, + Id = season.ShowID, Slug = "new-slug" }; await Repositories.LibraryManager.ShowRepository.Edit(show, false); @@ -70,7 +70,7 @@ namespace Kyoo.Tests.Database Assert.Equal("anohana-s1", season.Slug); await _repository.Edit(new Season { - ID = 1, + Id = 1, SeasonNumber = 2, ShowID = 1 }, false); @@ -83,7 +83,7 @@ namespace Kyoo.Tests.Database { Season season = await _repository.Create(new Season { - ShowID = TestSample.Get().ID, + ShowID = TestSample.Get().Id, SeasonNumber = 2 }); Assert.Equal($"{TestSample.Get().Slug}-s2", season.Slug); @@ -95,17 +95,17 @@ namespace Kyoo.Tests.Database Season season = TestSample.GetNew(); season.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, - new MetadataID + new MetadataId { Provider = TestSample.GetNew(), Link = "new-provider-link", - DataID = "new-id" + DataId = "new-id" } }; await _repository.Create(season); @@ -140,11 +140,11 @@ namespace Kyoo.Tests.Database Season value = await _repository.Get(TestSample.Get().Slug); value.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, }; await _repository.Edit(value, false); @@ -162,13 +162,13 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Season value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new List { new() { Provider = TestSample.Get(), Link = "link", - DataID = "id" + DataId = "id" }, }; await _repository.Edit(value, false); @@ -183,11 +183,11 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataID + value.ExternalId.Add(new MetadataId { Provider = TestSample.GetNew(), Link = "link", - DataID = "id" + DataId = "id" }); await _repository.Edit(value, false); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index b8a40ae5..ac806a28 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -159,7 +159,7 @@ namespace Kyoo.Tests.Database Show edited = await _repository.Edit(value, false); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(edited.People.First().ShowID, value.ID); + Assert.Equal(edited.People.First().ShowID, value.Id); Assert.Equal( value.People.Select(x => new { x.Role, x.Slug, x.People.Name }), edited.People.Select(x => new { x.Role, x.Slug, x.People.Name })); @@ -182,10 +182,10 @@ namespace Kyoo.Tests.Database Show value = await _repository.Get(TestSample.Get().Slug); value.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = new Provider("test", "test.png"), - DataID = "1234" + DataId = "1234" } }; Show edited = await _repository.Edit(value, false); @@ -213,14 +213,14 @@ namespace Kyoo.Tests.Database Show value = await _repository.Get(TestSample.Get().Slug); Show newValue = new() { - ID = value.ID, + Id = value.Id, Slug = "reset", Name = "Reset" }; Show edited = await _repository.Edit(newValue, true); - Assert.Equal(value.ID, edited.ID); + Assert.Equal(value.Id, edited.Id); Assert.Null(edited.Overview); Assert.Equal("reset", edited.Slug); Assert.Equal("Reset", edited.Name); @@ -235,14 +235,14 @@ namespace Kyoo.Tests.Database public async Task CreateWithRelationsTest() { Show expected = TestSample.Get(); - expected.ID = 0; + expected.Id = 0; expected.Slug = "created-relation-test"; expected.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = new Provider("provider", "provider.png"), - DataID = "ID" + DataId = "ID" } }; expected.Genres = new[] @@ -275,7 +275,7 @@ namespace Kyoo.Tests.Database .Include(x => x.People) .ThenInclude(x => x.People) .Include(x => x.Studio) - .FirstAsync(x => x.ID == created.ID); + .FirstAsync(x => x.Id == created.Id); retrieved.People.ForEach(x => { x.Show = null; @@ -298,14 +298,14 @@ namespace Kyoo.Tests.Database public async Task CreateWithExternalID() { Show expected = TestSample.Get(); - expected.ID = 0; + expected.Id = 0; expected.Slug = "created-relation-test"; expected.ExternalId = new[] { - new MetadataID + new MetadataId { Provider = TestSample.Get(), - DataID = "ID" + DataId = "ID" } }; Show created = await _repository.Create(expected); @@ -314,7 +314,7 @@ namespace Kyoo.Tests.Database Show retrieved = await context.Shows .Include(x => x.ExternalId) .ThenInclude(x => x.Provider) - .FirstAsync(x => x.ID == created.ID); + .FirstAsync(x => x.Id == created.Id); KAssert.DeepEqual(expected, retrieved); Assert.Single(retrieved.ExternalId); Assert.Equal("ID", retrieved.ExternalId.First().DataID); @@ -324,7 +324,7 @@ namespace Kyoo.Tests.Database public async Task SlugDuplicationTest() { Show test = TestSample.Get(); - test.ID = 0; + test.Id = 0; test.Slug = "300"; Show created = await _repository.Create(test); Assert.Equal("300!", created.Slug); @@ -334,7 +334,7 @@ namespace Kyoo.Tests.Database public async Task GetSlugTest() { Show reference = TestSample.Get(); - Assert.Equal(reference.Slug, await _repository.GetSlug(reference.ID)); + Assert.Equal(reference.Slug, await _repository.GetSlug(reference.Id)); } [Theory] diff --git a/back/tests/Kyoo.Tests/Database/TestSample.cs b/back/tests/Kyoo.Tests/Database/TestSample.cs index 5fbe23a8..380830e0 100644 --- a/back/tests/Kyoo.Tests/Database/TestSample.cs +++ b/back/tests/Kyoo.Tests/Database/TestSample.cs @@ -31,7 +31,7 @@ namespace Kyoo.Tests typeof(Collection), () => new Collection { - ID = 2, + Id = 2, Slug = "new-collection", Name = "New Collection", Overview = "A collection created by new sample", @@ -45,7 +45,7 @@ namespace Kyoo.Tests typeof(Show), () => new Show { - ID = 2, + Id = 2, Slug = "new-show", Name = "New Show", Overview = "overview", @@ -66,7 +66,7 @@ namespace Kyoo.Tests typeof(Season), () => new Season { - ID = 2, + Id = 2, ShowID = 1, ShowSlug = Get().Slug, Name = "New season", @@ -84,7 +84,7 @@ namespace Kyoo.Tests typeof(Episode), () => new Episode { - ID = 2, + Id = 2, ShowID = 1, ShowSlug = Get().Slug, SeasonID = 1, @@ -118,7 +118,7 @@ namespace Kyoo.Tests typeof(People), () => new People { - ID = 2, + Id = 2, Slug = "new-person-name", Name = "New person name", Images = new Dictionary @@ -146,7 +146,7 @@ namespace Kyoo.Tests typeof(Collection), () => new Collection { - ID = 1, + Id = 1, Slug = "collection", Name = "Collection", Overview = "A nice collection for tests", @@ -160,7 +160,7 @@ namespace Kyoo.Tests typeof(Show), () => new Show { - ID = 1, + Id = 1, Slug = "anohana", Name = "Anohana: The Flower We Saw That Day", Aliases = new[] @@ -190,7 +190,7 @@ namespace Kyoo.Tests typeof(Season), () => new Season { - ID = 1, + Id = 1, ShowSlug = "anohana", ShowID = 1, SeasonNumber = 1, @@ -210,7 +210,7 @@ namespace Kyoo.Tests typeof(Episode), () => new Episode { - ID = 1, + Id = 1, ShowSlug = "anohana", ShowID = 1, SeasonID = 1, @@ -233,7 +233,7 @@ namespace Kyoo.Tests typeof(People), () => new People { - ID = 1, + Id = 1, Slug = "the-actor", Name = "The Actor", Images = new Dictionary @@ -248,7 +248,7 @@ namespace Kyoo.Tests typeof(Studio), () => new Studio { - ID = 1, + Id = 1, Slug = "hyper-studio", Name = "Hyper studio", } @@ -281,7 +281,7 @@ namespace Kyoo.Tests typeof(User), () => new User { - ID = 1, + Id = 1, Slug = "user", Username = "User", Email = "user@im-a-user.com", @@ -304,22 +304,22 @@ namespace Kyoo.Tests public static void FillDatabase(DatabaseContext context) { Collection collection = Get(); - collection.ID = 0; + collection.Id = 0; context.Collections.Add(collection); Show show = Get(); - show.ID = 0; + show.Id = 0; show.StudioID = 0; context.Shows.Add(show); Season season = Get(); - season.ID = 0; + season.Id = 0; season.ShowID = 0; season.Show = show; context.Seasons.Add(season); Episode episode = Get(); - episode.ID = 0; + episode.Id = 0; episode.ShowID = 0; episode.Show = show; episode.SeasonID = 0; @@ -327,7 +327,7 @@ namespace Kyoo.Tests context.Episodes.Add(episode); Studio studio = Get(); - studio.ID = 0; + studio.Id = 0; studio.Shows = new List { show }; context.Studios.Add(studio); @@ -337,7 +337,7 @@ namespace Kyoo.Tests context.Genres.Add(genre); People people = Get(); - people.ID = 0; + people.Id = 0; context.People.Add(people); Library library = Get(); @@ -346,7 +346,7 @@ namespace Kyoo.Tests context.Libraries.Add(library); User user = Get(); - user.ID = 0; + user.Id = 0; context.Users.Add(user); context.SaveChanges(); @@ -356,7 +356,7 @@ namespace Kyoo.Tests { return new() { - ID = 2, + Id = 2, ShowSlug = "anohana", ShowID = 1, SeasonNumber = null, @@ -379,7 +379,7 @@ namespace Kyoo.Tests { return new() { - ID = 3, + Id = 3, ShowSlug = "anohana", ShowID = 1, Path = "/home/kyoo/john-wick", diff --git a/back/tests/Kyoo.Tests/Utility/MergerTests.cs b/back/tests/Kyoo.Tests/Utility/MergerTests.cs index 2712cacf..a6528875 100644 --- a/back/tests/Kyoo.Tests/Utility/MergerTests.cs +++ b/back/tests/Kyoo.Tests/Utility/MergerTests.cs @@ -223,7 +223,7 @@ namespace Kyoo.Tests.Utility { Show test = new() { - ID = 5, + Id = 5, Genres = new[] { new Genre("test") } }; Show test2 = new() @@ -236,7 +236,7 @@ namespace Kyoo.Tests.Utility }; Show ret = Merger.Merge(test, test2); Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); + Assert.Equal(5, ret.Id); Assert.Equal(2, ret.Genres.Count); Assert.Equal("test", ret.Genres.ToArray()[0].Slug); @@ -351,7 +351,7 @@ namespace Kyoo.Tests.Utility { Collection collection = new() { - ID = 5, + Id = 5, Name = "merged", }; Collection collection2 = new() @@ -360,7 +360,7 @@ namespace Kyoo.Tests.Utility }; Collection ret = Merger.Complete(collection, collection2); Assert.True(ReferenceEquals(collection, ret)); - Assert.Equal(5, ret.ID); + Assert.Equal(5, ret.Id); Assert.Equal("test", ret.Name); Assert.Null(ret.Slug); } diff --git a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs index 8662c4ed..f7de596e 100644 --- a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs +++ b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs @@ -32,8 +32,8 @@ namespace Kyoo.Tests.Utility [Fact] public void IsPropertyExpression_Tests() { - Expression> member = x => x.ID; - Expression> memberCast = x => x.ID; + Expression> member = x => x.Id; + Expression> memberCast = x => x.Id; Assert.False(KUtility.IsPropertyExpression(null)); Assert.True(KUtility.IsPropertyExpression(member)); @@ -46,8 +46,8 @@ namespace Kyoo.Tests.Utility [Fact] public void GetPropertyName_Test() { - Expression> member = x => x.ID; - Expression> memberCast = x => x.ID; + Expression> member = x => x.Id; + Expression> memberCast = x => x.Id; Assert.Equal("ID", KUtility.GetPropertyName(member)); Assert.Equal("ID", KUtility.GetPropertyName(memberCast)); diff --git a/scanner/providers/implementations/themoviedatabase.py b/scanner/providers/implementations/themoviedatabase.py index 2b86dda5..00b23aaf 100644 --- a/scanner/providers/implementations/themoviedatabase.py +++ b/scanner/providers/implementations/themoviedatabase.py @@ -89,7 +89,7 @@ class TheMovieDatabase(Provider): logos=[f"https://image.tmdb.org/t/p/original{company['logo_path']}"] if "logo_path" in company else [], - external_ids={ + external_id={ self.name: MetadataID( company["id"], f"https://www.themoviedb.org/company/{company['id']}" ) @@ -135,15 +135,24 @@ class TheMovieDatabase(Provider): for x in movie["genres"] if x["id"] in self.genre_map ], - external_ids={ - self.name: MetadataID( - movie["id"], f"https://www.themoviedb.org/movie/{movie['id']}" - ), - "imdb": MetadataID( - movie["imdb_id"], - f"https://www.imdb.com/title/{movie['imdb_id']}", - ), - } + external_id=( + { + self.name: MetadataID( + movie["id"], + f"https://www.themoviedb.org/movie/{movie['id']}", + ) + } + | ( + { + "imdb": MetadataID( + movie["imdb_id"], + f"https://www.imdb.com/title/{movie['imdb_id']}", + ) + } + if movie["imdb_id"] + else {} + ) + ) # TODO: Add cast information ) translation = MovieTranslation( @@ -171,7 +180,7 @@ class TheMovieDatabase(Provider): *, language: list[str], ) -> Show: - show_id = show.external_ids[self.name].id + show_id = show.external_id[self.name].data_id if show.original_language not in language: language.append(show.original_language) @@ -206,7 +215,7 @@ class TheMovieDatabase(Provider): for x in show["genres"] if x["id"] in self.genre_map ], - external_ids={ + external_id={ self.name: MetadataID( show["id"], f"https://www.themoviedb.org/tv/{show['id']}" ), @@ -271,7 +280,7 @@ class TheMovieDatabase(Provider): if season["air_date"] else None, end_air=None, - external_ids={ + external_id={ self.name: MetadataID( season["id"], f"https://www.themoviedb.org/tv/{show_id}/season/{season['season_number']}", @@ -329,7 +338,7 @@ class TheMovieDatabase(Provider): show=PartialShow( name=search["name"], original_language=search["original_language"], - external_ids={ + external_id={ self.name: MetadataID( show_id, f"https://www.themoviedb.org/tv/{show_id}" ) @@ -345,7 +354,7 @@ class TheMovieDatabase(Provider): thumbnail=f"https://image.tmdb.org/t/p/original{episode['still_path']}" if "still_path" in episode and episode["still_path"] is not None else None, - external_ids={ + external_id={ self.name: MetadataID( episode["id"], f"https://www.themoviedb.org/tv/{show_id}/season/{episode['season_number']}/episode/{episode['episode_number']}", diff --git a/scanner/providers/types/episode.py b/scanner/providers/types/episode.py index 72723b13..e374e0a7 100644 --- a/scanner/providers/types/episode.py +++ b/scanner/providers/types/episode.py @@ -11,7 +11,7 @@ from .metadataid import MetadataID class PartialShow: name: str original_language: str - external_ids: dict[str, MetadataID] + external_id: dict[str, MetadataID] @dataclass @@ -28,7 +28,7 @@ class Episode: absolute_number: Optional[int] release_date: Optional[date | int] thumbnail: Optional[str] - external_ids: dict[str, MetadataID] + external_id: dict[str, MetadataID] path: Optional[str] = None show_id: Optional[str] = None diff --git a/scanner/providers/types/metadataid.py b/scanner/providers/types/metadataid.py index 9b163e39..87bfc915 100644 --- a/scanner/providers/types/metadataid.py +++ b/scanner/providers/types/metadataid.py @@ -4,5 +4,5 @@ from typing import Optional @dataclass class MetadataID: - id: str + data_id: str link: Optional[str] diff --git a/scanner/providers/types/movie.py b/scanner/providers/types/movie.py index c4e7eeb4..1f70b8e3 100644 --- a/scanner/providers/types/movie.py +++ b/scanner/providers/types/movie.py @@ -40,7 +40,7 @@ class Movie: genres: list[Genre] = field(default_factory=list) # TODO: handle staff # staff: list[Staff] - external_ids: dict[str, MetadataID] = field(default_factory=dict) + external_id: dict[str, MetadataID] = field(default_factory=dict) translations: dict[str, MovieTranslation] = field(default_factory=dict) diff --git a/scanner/providers/types/season.py b/scanner/providers/types/season.py index b4e68170..c234364e 100644 --- a/scanner/providers/types/season.py +++ b/scanner/providers/types/season.py @@ -19,7 +19,7 @@ class Season: season_number: int start_air: Optional[date | int] = None end_air: Optional[date | int] = None - external_ids: dict[str, MetadataID] = field(default_factory=dict) + external_id: dict[str, MetadataID] = field(default_factory=dict) show_id: Optional[str] = None translations: dict[str, SeasonTranslation] = field(default_factory=dict) diff --git a/scanner/providers/types/show.py b/scanner/providers/types/show.py index a7229519..069eff3e 100644 --- a/scanner/providers/types/show.py +++ b/scanner/providers/types/show.py @@ -42,7 +42,7 @@ class Show: seasons: list[Season] # TODO: handle staff # staff: list[Staff] - external_ids: dict[str, MetadataID] + external_id: dict[str, MetadataID] translations: dict[str, ShowTranslation] = field(default_factory=dict) diff --git a/scanner/providers/types/studio.py b/scanner/providers/types/studio.py index 7f7b3149..756332a3 100644 --- a/scanner/providers/types/studio.py +++ b/scanner/providers/types/studio.py @@ -7,7 +7,7 @@ from .metadataid import MetadataID class Studio: name: str logos: list[str] = field(default_factory=list) - external_ids: dict[str, MetadataID] = field(default_factory=dict) + external_id: dict[str, MetadataID] = field(default_factory=dict) def to_kyoo(self): return { diff --git a/scanner/scanner/__init__.py b/scanner/scanner/__init__.py index c13d1108..f4eb657b 100644 --- a/scanner/scanner/__init__.py +++ b/scanner/scanner/__init__.py @@ -31,6 +31,7 @@ async def main(): if len(sys.argv) > 1 and sys.argv[1] == "-vv": logging.basicConfig(level=logging.DEBUG) logging.getLogger("watchfiles").setLevel(logging.WARNING) + logging.getLogger("rebulk").setLevel(logging.WARNING) jsons.set_serializer(lambda x, **_: format_date(x), Optional[date | int]) # type: ignore async with ClientSession( diff --git a/scanner/scanner/scanner.py b/scanner/scanner/scanner.py index babda259..48036feb 100644 --- a/scanner/scanner/scanner.py +++ b/scanner/scanner/scanner.py @@ -130,7 +130,7 @@ class Scanner: return ret # The parameter is only used as a key for the cache. - provider_id = episode.show.external_ids[self.provider.name].id + provider_id = episode.show.external_id[self.provider.name].data_id return await create_show(provider_id) @provider_cache("seasons") From ca994216242942f265dfea8762a2579115d811f1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 6 Aug 2023 12:42:51 +0900 Subject: [PATCH 04/37] Update library items for new movies --- .../Kyoo.Abstractions/Models/LibraryItem.cs | 135 +- .../Models/Resources/Collection.cs | 10 +- .../Models/Resources/Movie.cs | 7 +- .../Models/Resources/Show.cs | 3 +- .../Repositories/LibraryItemRepository.cs | 18 +- back/src/Kyoo.Postgresql/DatabaseContext.cs | 30 +- .../Migrations/20230805052627_initial.cs | 528 ------ ....cs => 20230806025737_initial.Designer.cs} | 241 ++- .../Migrations/20230806025737_initial.cs | 547 ++++++ .../20230806025743_items.Designer.cs | 1502 +++++++++++++++++ .../Migrations/20230806025743_items.cs | 64 + .../PostgresContextModelSnapshot.cs | 239 ++- back/src/Kyoo.Postgresql/PostgresContext.cs | 2 + shell.nix | 2 +- 14 files changed, 2644 insertions(+), 684 deletions(-) delete mode 100644 back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.cs rename back/src/Kyoo.Postgresql/Migrations/{20230805052627_initial.Designer.cs => 20230806025737_initial.Designer.cs} (86%) create mode 100644 back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs create mode 100644 back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs create mode 100644 back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs diff --git a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs index 677f27ca..5961adbc 100644 --- a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs +++ b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs @@ -17,6 +17,11 @@ // along with Kyoo. If not, see . using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Utils; namespace Kyoo.Abstractions.Models { @@ -53,60 +58,114 @@ namespace Kyoo.Abstractions.Models /// public ItemKind Kind { get; } + /// + /// The title of this show. + /// public string Name { get; } - public DateTime? AirDate { get; } + /// + /// The summary of this show. + /// + public string? Overview { get; } - public Image Poster { get; } + /// + /// The date this movie aired. + /// + public DateTime? AirDate { get; } } - public class BagItem : ILibraryItem + public class LibraryItem : IResource, ILibraryItem, IThumbnails, IMetadata { - public ItemKind Kind { get; } - + /// public int Id { get; set; } - public string Slug { get; set; } + /// + [MaxLength(256)] + public string Slug { get; set; } + /// + /// The title of this show. + /// public string Name { get; set; } + /// + /// A catchphrase for this movie. + /// + public string? Tagline { get; set; } + + /// + /// The list of alternative titles of this show. + /// + public string[] Aliases { get; set; } = Array.Empty(); + + /// + /// The path of the movie video file. + /// + public string? Path { get; set; } + + /// + /// The summary of this show. + /// + public string? Overview { get; set; } + + /// + /// A list of tags that match this movie. + /// + public string[] Tags { get; set; } = Array.Empty(); + + /// + /// The list of genres (themes) this show has. + /// + public Genre[] Genres { get; set; } = Array.Empty(); + + /// + /// Is this show airing, not aired yet or finished? + /// + public Status Status { get; set; } + + /// + /// The date this show started airing. It can be null if this is unknown. + /// + public DateTime? StartAir { get; set; } + + /// + /// The date this show finished airing. + /// It can also be null if this is unknown. + /// + public DateTime? EndAir { get; set; } + + /// + /// The date this movie aired. + /// public DateTime? AirDate { get; set; } - public Image Poster { get; set; } + /// + public Image? Poster { get; set; } - public object Rest { get; set; } + /// + public Image? Thumbnail { get; set; } - public ILibraryItem ToItem() + /// + public Image? Logo { get; set; } + + /// + /// A video of a few minutes that tease the content. + /// + public string? Trailer { get; set; } + + /// + public ItemKind Kind => ItemKind.Movie; + + /// + public Dictionary ExternalId { get; set; } = new(); + + public LibraryItem() { } + + [JsonConstructor] + public LibraryItem(string name) { - return Kind switch - { - ItemKind.Movie => Rest as MovieItem, - ItemKind.Show => Rest as ShowItem, - ItemKind.Collection => Rest as CollectionItem, - }; + Slug = Utility.ToSlug(name); + Name = name; } } - - public sealed class ShowItem : Show, ILibraryItem - { - /// - public ItemKind Kind => ItemKind.Show; - - public DateTime? AirDate => StartAir; - } - - public sealed class MovieItem : Movie, ILibraryItem - { - /// - public ItemKind Kind => ItemKind.Movie; - } - - public sealed class CollectionItem : Collection, ILibraryItem - { - /// - public ItemKind Kind => ItemKind.Collection; - - public DateTime? AirDate => null; - } - } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs index 544e6a17..6cae5231 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs @@ -41,6 +41,11 @@ namespace Kyoo.Abstractions.Models /// public string Name { get; set; } + /// + /// The description of this collection. + /// + public string? Overview { get; set; } + /// public Image? Poster { get; set; } @@ -50,11 +55,6 @@ namespace Kyoo.Abstractions.Models /// public Image? Logo { get; set; } - /// - /// The description of this collection. - /// - public string? Overview { get; set; } - /// /// The list of movies contained in this collection. /// diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index 13943ff2..99a9f854 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -124,8 +124,11 @@ namespace Kyoo.Abstractions.Models /// public void OnMerge(object merged) { - foreach (PeopleRole link in People) - link.Movie = this; + if (People != null) + { + foreach (PeopleRole link in People) + link.Movie = this; + } } public Movie() { } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs index a73b8b21..38a4a42e 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -80,7 +80,6 @@ namespace Kyoo.Abstractions.Models /// /// The date this show finished airing. - /// It must be after the but can be the same (example: for movies). /// It can also be null if this is unknown. /// public DateTime? EndAir { get; set; } @@ -99,6 +98,8 @@ namespace Kyoo.Abstractions.Models /// public string? Trailer { get; set; } + [SerializeIgnore] public DateTime? AirDate => StartAir; + /// public Dictionary ExternalId { get; set; } = new(); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index 593407aa..3414b63c 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -54,13 +54,13 @@ namespace Kyoo.Core.Controllers /// public override async Task GetOrDefault(int id) { - return (await _database.LibraryItems.FirstOrDefaultAsync(x => x.Id == id)).ToItem(); + return await _database.LibraryItems.FirstOrDefaultAsync(x => x.Id == id); } /// public override async Task GetOrDefault(string slug) { - return (await _database.LibraryItems.SingleOrDefaultAsync(x => x.Slug == slug)).ToItem(); + return await _database.LibraryItems.SingleOrDefaultAsync(x => x.Slug == slug); } /// @@ -68,9 +68,9 @@ namespace Kyoo.Core.Controllers Sort sort = default, Pagination limit = default) { - return (await ApplyFilters(_database.LibraryItems, where, sort, limit)) - .Select(x => (x as BagItem)!.ToItem()) - .ToList(); + return await ApplyFilters(_database.LibraryItems, where, sort, limit); + // .Select(x => x.ToItem()) + // .ToList(); } /// @@ -85,14 +85,12 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - return (await Sort( + return await Sort( _database.LibraryItems - .Where(_database.Like(x => x.Name, $"%{query}%")) + .Where(_database.Like(x => x.Name, $"%{query}%")) ) .Take(20) - .ToListAsync()) - .Select(x => (x as BagItem)!.ToItem()) - .ToList(); + .ToListAsync(); } /// diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index a5d1828a..7982c6e6 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -92,32 +92,7 @@ namespace Kyoo.Postgresql /// /// This set is ready only, on most database this will be a view. /// - public IQueryable LibraryItems => - Shows.Select(x => new BagItem - { - Id = x.Id, - Slug = x.Slug, - Name = x.Name, - AirDate = x.StartAir, - Poster = x.Poster, - Rest = x - }).Union(Movies.Select(x => new BagItem - { - Id = x.Id, - Slug = x.Slug, - Name = x.Name, - AirDate = x.AirDate, - Poster = x.Poster, - Rest = x - })).Union(Collections.Select(x => new BagItem - { - Id = x.Id, - Slug = x.Slug, - Name = x.Name, - AirDate = null, - Poster = x.Poster, - Rest = x - })); + public DbSet LibraryItems { get; set; } /// /// Add a many to many link between two resources. @@ -281,6 +256,7 @@ namespace Kyoo.Postgresql .WithMany(x => x.Shows) .OnDelete(DeleteBehavior.SetNull); + _HasManyToMany(modelBuilder, x => x.Movies, x => x.Collections); _HasManyToMany(modelBuilder, x => x.Shows, x => x.Collections); modelBuilder.Entity() @@ -288,6 +264,7 @@ namespace Kyoo.Postgresql .WithMany("Users") .UsingEntity(x => x.ToTable(LinkName())); + _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); @@ -296,6 +273,7 @@ namespace Kyoo.Postgresql _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); + _HasImages(modelBuilder); _HasImages(modelBuilder); _HasImages(modelBuilder); _HasImages(modelBuilder); diff --git a/back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.cs b/back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.cs deleted file mode 100644 index 40e8dd3a..00000000 --- a/back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.cs +++ /dev/null @@ -1,528 +0,0 @@ -using System; -using Kyoo.Abstractions.Models; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Kyoo.Postgresql.Migrations -{ - /// - public partial class initial : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterDatabase() - .Annotation("Npgsql:Enum:genre", "action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western") - .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned"); - - migrationBuilder.CreateTable( - name: "collections", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - name = table.Column(type: "text", nullable: false), - poster_source = table.Column(type: "text", nullable: true), - poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - thumbnail_source = table.Column(type: "text", nullable: true), - thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - logo_source = table.Column(type: "text", nullable: true), - logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - overview = table.Column(type: "text", nullable: true), - external_id = table.Column(type: "json", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_collections", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "people", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - name = table.Column(type: "text", nullable: false), - poster_source = table.Column(type: "text", nullable: true), - poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - thumbnail_source = table.Column(type: "text", nullable: true), - thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - logo_source = table.Column(type: "text", nullable: true), - logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - external_id = table.Column(type: "json", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_people", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "studios", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - name = table.Column(type: "text", nullable: false), - external_id = table.Column(type: "json", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_studios", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "users", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - username = table.Column(type: "text", nullable: false), - email = table.Column(type: "text", nullable: false), - password = table.Column(type: "text", nullable: false), - permissions = table.Column(type: "text[]", nullable: false), - logo_source = table.Column(type: "text", nullable: true), - logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_users", x => x.id); - }); - - migrationBuilder.CreateTable( - name: "movies", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - name = table.Column(type: "text", nullable: false), - tagline = table.Column(type: "text", nullable: true), - aliases = table.Column(type: "text[]", nullable: false), - path = table.Column(type: "text", nullable: false), - overview = table.Column(type: "text", nullable: true), - tags = table.Column(type: "text[]", nullable: false), - genres = table.Column(type: "genre[]", nullable: false), - status = table.Column(type: "status", nullable: false), - air_date = table.Column(type: "timestamp with time zone", nullable: true), - poster_source = table.Column(type: "text", nullable: true), - poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - thumbnail_source = table.Column(type: "text", nullable: true), - thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - logo_source = table.Column(type: "text", nullable: true), - logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - trailer = table.Column(type: "text", nullable: true), - external_id = table.Column(type: "json", nullable: false), - studio_id = table.Column(type: "integer", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_movies", x => x.id); - table.ForeignKey( - name: "fk_movies_studios_studio_id", - column: x => x.studio_id, - principalTable: "studios", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); - }); - - migrationBuilder.CreateTable( - name: "shows", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - name = table.Column(type: "text", nullable: false), - tagline = table.Column(type: "text", nullable: true), - aliases = table.Column(type: "text[]", nullable: false), - overview = table.Column(type: "text", nullable: true), - tags = table.Column(type: "text[]", nullable: false), - genres = table.Column(type: "genre[]", nullable: false), - status = table.Column(type: "status", nullable: false), - start_air = table.Column(type: "timestamp with time zone", nullable: true), - end_air = table.Column(type: "timestamp with time zone", nullable: true), - poster_source = table.Column(type: "text", nullable: true), - poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - thumbnail_source = table.Column(type: "text", nullable: true), - thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - logo_source = table.Column(type: "text", nullable: true), - logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - trailer = table.Column(type: "text", nullable: true), - external_id = table.Column(type: "json", nullable: false), - studio_id = table.Column(type: "integer", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_shows", x => x.id); - table.ForeignKey( - name: "fk_shows_studios_studio_id", - column: x => x.studio_id, - principalTable: "studios", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); - }); - - migrationBuilder.CreateTable( - name: "collection_movie", - columns: table => new - { - collections_id = table.Column(type: "integer", nullable: false), - movies_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_collection_movie", x => new { x.collections_id, x.movies_id }); - table.ForeignKey( - name: "fk_collection_movie_collections_collections_id", - column: x => x.collections_id, - principalTable: "collections", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_collection_movie_movies_movies_id", - column: x => x.movies_id, - principalTable: "movies", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_collection_show", - columns: table => new - { - collection_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id }); - table.ForeignKey( - name: "fk_link_collection_show_collections_collection_id", - column: x => x.collection_id, - principalTable: "collections", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_collection_show_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "link_user_show", - columns: table => new - { - users_id = table.Column(type: "integer", nullable: false), - watched_id = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id }); - table.ForeignKey( - name: "fk_link_user_show_shows_watched_id", - column: x => x.watched_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_link_user_show_users_users_id", - column: x => x.users_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "people_roles", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - people_id = table.Column(type: "integer", nullable: false), - show_id = table.Column(type: "integer", nullable: true), - movie_id = table.Column(type: "integer", nullable: true), - type = table.Column(type: "text", nullable: false), - role = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_people_roles", x => x.id); - table.ForeignKey( - name: "fk_people_roles_movies_movie_id", - column: x => x.movie_id, - principalTable: "movies", - principalColumn: "id"); - table.ForeignKey( - name: "fk_people_roles_people_people_id", - column: x => x.people_id, - principalTable: "people", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_people_roles_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id"); - }); - - migrationBuilder.CreateTable( - name: "seasons", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - show_id = table.Column(type: "integer", nullable: false), - season_number = table.Column(type: "integer", nullable: false), - name = table.Column(type: "text", nullable: true), - overview = table.Column(type: "text", nullable: true), - start_date = table.Column(type: "timestamp with time zone", nullable: true), - end_date = table.Column(type: "timestamp with time zone", nullable: true), - poster_source = table.Column(type: "text", nullable: true), - poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - thumbnail_source = table.Column(type: "text", nullable: true), - thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - logo_source = table.Column(type: "text", nullable: true), - logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - external_id = table.Column(type: "json", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_seasons", x => x.id); - table.ForeignKey( - name: "fk_seasons_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "episodes", - columns: table => new - { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - show_id = table.Column(type: "integer", nullable: false), - season_id = table.Column(type: "integer", nullable: true), - season_number = table.Column(type: "integer", nullable: true), - episode_number = table.Column(type: "integer", nullable: true), - absolute_number = table.Column(type: "integer", nullable: true), - path = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: true), - overview = table.Column(type: "text", nullable: true), - release_date = table.Column(type: "timestamp with time zone", nullable: true), - poster_source = table.Column(type: "text", nullable: true), - poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - thumbnail_source = table.Column(type: "text", nullable: true), - thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - logo_source = table.Column(type: "text", nullable: true), - logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - external_id = table.Column(type: "json", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_episodes", x => x.id); - table.ForeignKey( - name: "fk_episodes_seasons_season_id", - column: x => x.season_id, - principalTable: "seasons", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_episodes_shows_show_id", - column: x => x.show_id, - principalTable: "shows", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "watched_episode", - columns: table => new - { - user_id = table.Column(type: "integer", nullable: false), - episode_id = table.Column(type: "integer", nullable: false), - watched_percentage = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_watched_episode", x => new { x.user_id, x.episode_id }); - table.ForeignKey( - name: "fk_watched_episode_episodes_episode_id", - column: x => x.episode_id, - principalTable: "episodes", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_watched_episode_users_user_id", - column: x => x.user_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_collection_movie_movies_id", - table: "collection_movie", - column: "movies_id"); - - migrationBuilder.CreateIndex( - name: "ix_collections_slug", - table: "collections", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_episodes_season_id", - table: "episodes", - column: "season_id"); - - migrationBuilder.CreateIndex( - name: "ix_episodes_show_id_season_number_episode_number_absolute_numb", - table: "episodes", - columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_episodes_slug", - table: "episodes", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_link_collection_show_show_id", - table: "link_collection_show", - column: "show_id"); - - migrationBuilder.CreateIndex( - name: "ix_link_user_show_watched_id", - table: "link_user_show", - column: "watched_id"); - - migrationBuilder.CreateIndex( - name: "ix_movies_slug", - table: "movies", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_movies_studio_id", - table: "movies", - column: "studio_id"); - - migrationBuilder.CreateIndex( - name: "ix_people_slug", - table: "people", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_people_roles_movie_id", - table: "people_roles", - column: "movie_id"); - - migrationBuilder.CreateIndex( - name: "ix_people_roles_people_id", - table: "people_roles", - column: "people_id"); - - migrationBuilder.CreateIndex( - name: "ix_people_roles_show_id", - table: "people_roles", - column: "show_id"); - - migrationBuilder.CreateIndex( - name: "ix_seasons_show_id_season_number", - table: "seasons", - columns: new[] { "show_id", "season_number" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_seasons_slug", - table: "seasons", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_shows_slug", - table: "shows", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_shows_studio_id", - table: "shows", - column: "studio_id"); - - migrationBuilder.CreateIndex( - name: "ix_studios_slug", - table: "studios", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_users_slug", - table: "users", - column: "slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_watched_episode_episode_id", - table: "watched_episode", - column: "episode_id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "collection_movie"); - - migrationBuilder.DropTable( - name: "link_collection_show"); - - migrationBuilder.DropTable( - name: "link_user_show"); - - migrationBuilder.DropTable( - name: "people_roles"); - - migrationBuilder.DropTable( - name: "watched_episode"); - - migrationBuilder.DropTable( - name: "collections"); - - migrationBuilder.DropTable( - name: "movies"); - - migrationBuilder.DropTable( - name: "people"); - - migrationBuilder.DropTable( - name: "episodes"); - - migrationBuilder.DropTable( - name: "users"); - - migrationBuilder.DropTable( - name: "seasons"); - - migrationBuilder.DropTable( - name: "shows"); - - migrationBuilder.DropTable( - name: "studios"); - } - } -} diff --git a/back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.Designer.cs similarity index 86% rename from back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.Designer.cs rename to back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.Designer.cs index 8fa8ae08..b0ecb787 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230805052627_initial.Designer.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.Designer.cs @@ -13,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] - [Migration("20230805052627_initial")] + [Migration("20230806025737_initial")] partial class initial { /// @@ -25,28 +25,10 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "action", "adventure", "animation", "comedy", "crime", "documentary", "drama", "family", "fantasy", "history", "horror", "music", "mystery", "romance", "science_fiction", "thriller", "war", "western" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_kind", new[] { "show", "movie", "collection" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("CollectionMovie", b => - { - b.Property("CollectionsId") - .HasColumnType("integer") - .HasColumnName("collections_id"); - - b.Property("MoviesId") - .HasColumnType("integer") - .HasColumnName("movies_id"); - - b.HasKey("CollectionsId", "MoviesId") - .HasName("pk_collection_movie"); - - b.HasIndex("MoviesId") - .HasDatabaseName("ix_collection_movie_movies_id"); - - b.ToTable("collection_movie", (string)null); - }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => { b.Property("Id") @@ -160,6 +142,85 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("episodes", (string)null); }); + modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AirDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("air_date"); + + b.Property("Aliases") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("EndAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_air"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("StartAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_air"); + + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); + + b.Property("Tagline") + .HasColumnType("text") + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); + + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); + + b.HasKey("Id") + .HasName("pk_library_items"); + + b.ToTable("library_items", (string)null); + }); + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { b.Property("Id") @@ -584,6 +645,25 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("link_user_show", (string)null); }); + modelBuilder.Entity("link_collection_movie", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("movie_id") + .HasColumnType("integer") + .HasColumnName("movie_id"); + + b.HasKey("collection_id", "movie_id") + .HasName("pk_link_collection_movie"); + + b.HasIndex("movie_id") + .HasDatabaseName("ix_link_collection_movie_movie_id"); + + b.ToTable("link_collection_movie", (string)null); + }); + modelBuilder.Entity("link_collection_show", b => { b.Property("collection_id") @@ -603,23 +683,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("link_collection_show", (string)null); }); - modelBuilder.Entity("CollectionMovie", b => - { - b.HasOne("Kyoo.Abstractions.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_collection_movie_collections_collections_id"); - - b.HasOne("Kyoo.Abstractions.Models.Movie", null) - .WithMany() - .HasForeignKey("MoviesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_collection_movie_movies_movies_id"); - }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => { b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => @@ -811,6 +874,93 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Thumbnail"); }); + modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("LibraryItemId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("LibraryItemId"); + + b1.ToTable("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemId") + .HasConstraintName("fk_library_items_library_items_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("LibraryItemId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("LibraryItemId"); + + b1.ToTable("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemId") + .HasConstraintName("fk_library_items_library_items_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("LibraryItemId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("LibraryItemId"); + + b1.ToTable("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemId") + .HasConstraintName("fk_library_items_library_items_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Thumbnail"); + }); + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") @@ -1277,6 +1427,23 @@ namespace Kyoo.Postgresql.Migrations .HasConstraintName("fk_link_user_show_shows_watched_id"); }); + modelBuilder.Entity("link_collection_movie", b => + { + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_movie_collections_collection_id"); + + b.HasOne("Kyoo.Abstractions.Models.Movie", null) + .WithMany() + .HasForeignKey("movie_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_movie_movies_movie_id"); + }); + modelBuilder.Entity("link_collection_show", b => { b.HasOne("Kyoo.Abstractions.Models.Collection", null) diff --git a/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs b/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs new file mode 100644 index 00000000..ea2fd7d8 --- /dev/null +++ b/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs @@ -0,0 +1,547 @@ +// 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 Kyoo.Abstractions.Models; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Kyoo.Postgresql.Migrations +{ + /// + public partial class initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:Enum:genre", "action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western") + .Annotation("Npgsql:Enum:item_kind", "show,movie,collection") + .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned"); + + migrationBuilder.CreateTable( + name: "collections", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + overview = table.Column(type: "text", nullable: true), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_collections", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "people", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_people", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "studios", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_studios", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "users", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + username = table.Column(type: "text", nullable: false), + email = table.Column(type: "text", nullable: false), + password = table.Column(type: "text", nullable: false), + permissions = table.Column(type: "text[]", nullable: false), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_users", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "movies", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + tagline = table.Column(type: "text", nullable: true), + aliases = table.Column(type: "text[]", nullable: false), + path = table.Column(type: "text", nullable: false), + overview = table.Column(type: "text", nullable: true), + tags = table.Column(type: "text[]", nullable: false), + genres = table.Column(type: "genre[]", nullable: false), + status = table.Column(type: "status", nullable: false), + air_date = table.Column(type: "timestamp with time zone", nullable: true), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + trailer = table.Column(type: "text", nullable: true), + external_id = table.Column(type: "json", nullable: false), + studio_id = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_movies", x => x.id); + table.ForeignKey( + name: "fk_movies_studios_studio_id", + column: x => x.studio_id, + principalTable: "studios", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "shows", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + name = table.Column(type: "text", nullable: false), + tagline = table.Column(type: "text", nullable: true), + aliases = table.Column(type: "text[]", nullable: false), + overview = table.Column(type: "text", nullable: true), + tags = table.Column(type: "text[]", nullable: false), + genres = table.Column(type: "genre[]", nullable: false), + status = table.Column(type: "status", nullable: false), + start_air = table.Column(type: "timestamp with time zone", nullable: true), + end_air = table.Column(type: "timestamp with time zone", nullable: true), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + trailer = table.Column(type: "text", nullable: true), + external_id = table.Column(type: "json", nullable: false), + studio_id = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_shows", x => x.id); + table.ForeignKey( + name: "fk_shows_studios_studio_id", + column: x => x.studio_id, + principalTable: "studios", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "link_collection_movie", + columns: table => new + { + collection_id = table.Column(type: "integer", nullable: false), + movie_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_collection_movie", x => new { x.collection_id, x.movie_id }); + table.ForeignKey( + name: "fk_link_collection_movie_collections_collection_id", + column: x => x.collection_id, + principalTable: "collections", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_collection_movie_movies_movie_id", + column: x => x.movie_id, + principalTable: "movies", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_collection_show", + columns: table => new + { + collection_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id }); + table.ForeignKey( + name: "fk_link_collection_show_collections_collection_id", + column: x => x.collection_id, + principalTable: "collections", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_collection_show_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "link_user_show", + columns: table => new + { + users_id = table.Column(type: "integer", nullable: false), + watched_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id }); + table.ForeignKey( + name: "fk_link_user_show_shows_watched_id", + column: x => x.watched_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_link_user_show_users_users_id", + column: x => x.users_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "people_roles", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + people_id = table.Column(type: "integer", nullable: false), + show_id = table.Column(type: "integer", nullable: true), + movie_id = table.Column(type: "integer", nullable: true), + type = table.Column(type: "text", nullable: false), + role = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_people_roles", x => x.id); + table.ForeignKey( + name: "fk_people_roles_movies_movie_id", + column: x => x.movie_id, + principalTable: "movies", + principalColumn: "id"); + table.ForeignKey( + name: "fk_people_roles_people_people_id", + column: x => x.people_id, + principalTable: "people", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_people_roles_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "seasons", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + show_id = table.Column(type: "integer", nullable: false), + season_number = table.Column(type: "integer", nullable: false), + name = table.Column(type: "text", nullable: true), + overview = table.Column(type: "text", nullable: true), + start_date = table.Column(type: "timestamp with time zone", nullable: true), + end_date = table.Column(type: "timestamp with time zone", nullable: true), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_seasons", x => x.id); + table.ForeignKey( + name: "fk_seasons_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "episodes", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + show_id = table.Column(type: "integer", nullable: false), + season_id = table.Column(type: "integer", nullable: true), + season_number = table.Column(type: "integer", nullable: true), + episode_number = table.Column(type: "integer", nullable: true), + absolute_number = table.Column(type: "integer", nullable: true), + path = table.Column(type: "text", nullable: false), + name = table.Column(type: "text", nullable: true), + overview = table.Column(type: "text", nullable: true), + release_date = table.Column(type: "timestamp with time zone", nullable: true), + poster_source = table.Column(type: "text", nullable: true), + poster_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + thumbnail_source = table.Column(type: "text", nullable: true), + thumbnail_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + logo_source = table.Column(type: "text", nullable: true), + logo_blurhash = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + external_id = table.Column(type: "json", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_episodes", x => x.id); + table.ForeignKey( + name: "fk_episodes_seasons_season_id", + column: x => x.season_id, + principalTable: "seasons", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_episodes_shows_show_id", + column: x => x.show_id, + principalTable: "shows", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "watched_episode", + columns: table => new + { + user_id = table.Column(type: "integer", nullable: false), + episode_id = table.Column(type: "integer", nullable: false), + watched_percentage = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_watched_episode", x => new { x.user_id, x.episode_id }); + table.ForeignKey( + name: "fk_watched_episode_episodes_episode_id", + column: x => x.episode_id, + principalTable: "episodes", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_watched_episode_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_collections_slug", + table: "collections", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_episodes_season_id", + table: "episodes", + column: "season_id"); + + migrationBuilder.CreateIndex( + name: "ix_episodes_show_id_season_number_episode_number_absolute_numb", + table: "episodes", + columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_episodes_slug", + table: "episodes", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_link_collection_movie_movie_id", + table: "link_collection_movie", + column: "movie_id"); + + migrationBuilder.CreateIndex( + name: "ix_link_collection_show_show_id", + table: "link_collection_show", + column: "show_id"); + + migrationBuilder.CreateIndex( + name: "ix_link_user_show_watched_id", + table: "link_user_show", + column: "watched_id"); + + migrationBuilder.CreateIndex( + name: "ix_movies_slug", + table: "movies", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_movies_studio_id", + table: "movies", + column: "studio_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_slug", + table: "people", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_people_roles_movie_id", + table: "people_roles", + column: "movie_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_roles_people_id", + table: "people_roles", + column: "people_id"); + + migrationBuilder.CreateIndex( + name: "ix_people_roles_show_id", + table: "people_roles", + column: "show_id"); + + migrationBuilder.CreateIndex( + name: "ix_seasons_show_id_season_number", + table: "seasons", + columns: new[] { "show_id", "season_number" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_seasons_slug", + table: "seasons", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_shows_slug", + table: "shows", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_shows_studio_id", + table: "shows", + column: "studio_id"); + + migrationBuilder.CreateIndex( + name: "ix_studios_slug", + table: "studios", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_users_slug", + table: "users", + column: "slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_watched_episode_episode_id", + table: "watched_episode", + column: "episode_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "link_collection_movie"); + + migrationBuilder.DropTable( + name: "link_collection_show"); + + migrationBuilder.DropTable( + name: "link_user_show"); + + migrationBuilder.DropTable( + name: "people_roles"); + + migrationBuilder.DropTable( + name: "watched_episode"); + + migrationBuilder.DropTable( + name: "collections"); + + migrationBuilder.DropTable( + name: "movies"); + + migrationBuilder.DropTable( + name: "people"); + + migrationBuilder.DropTable( + name: "episodes"); + + migrationBuilder.DropTable( + name: "users"); + + migrationBuilder.DropTable( + name: "seasons"); + + migrationBuilder.DropTable( + name: "shows"); + + migrationBuilder.DropTable( + name: "studios"); + } + } +} diff --git a/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs new file mode 100644 index 00000000..29274d2e --- /dev/null +++ b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs @@ -0,0 +1,1502 @@ +// +using System; +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("20230806025743_items")] + partial class items + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "action", "adventure", "animation", "comedy", "crime", "documentary", "drama", "family", "fantasy", "history", "horror", "music", "mystery", "romance", "science_fiction", "thriller", "war", "western" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_kind", 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("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .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("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Path") + .IsRequired() + .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() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + 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.LibraryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AirDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("air_date"); + + b.Property("Aliases") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("EndAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_air"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("StartAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_air"); + + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); + + b.Property("Tagline") + .HasColumnType("text") + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); + + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); + + b.HasKey("Id") + .HasName("pk_library_items"); + + b.ToTable("library_items", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AirDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("air_date"); + + b.Property("Aliases") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); + + b.Property("StudioID") + .HasColumnType("integer") + .HasColumnName("studio_id"); + + b.Property("Tagline") + .HasColumnType("text") + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); + + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); + + b.HasKey("Id") + .HasName("pk_movies"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_movies_slug"); + + b.HasIndex("StudioID") + .HasDatabaseName("ix_movies_studio_id"); + + b.ToTable("movies", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .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("MovieID") + .HasColumnType("integer") + .HasColumnName("movie_id"); + + b.Property("PeopleID") + .HasColumnType("integer") + .HasColumnName("people_id"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role"); + + b.Property("ShowID") + .HasColumnType("integer") + .HasColumnName("show_id"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_people_roles"); + + b.HasIndex("MovieID") + .HasDatabaseName("ix_people_roles_movie_id"); + + 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("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + 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() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + 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") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("EndAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_air"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .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("Tagline") + .HasColumnType("text") + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); + + 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("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .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") + .IsRequired() + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("permissions"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("Username") + .IsRequired() + .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_episode"); + + b.HasIndex("EpisodeID") + .HasDatabaseName("ix_watched_episode_episode_id"); + + b.ToTable("watched_episode", (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_movie", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("movie_id") + .HasColumnType("integer") + .HasColumnName("movie_id"); + + b.HasKey("collection_id", "movie_id") + .HasName("pk_link_collection_movie"); + + b.HasIndex("movie_id") + .HasDatabaseName("ix_link_collection_movie_movie_id"); + + b.ToTable("link_collection_movie", (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("Kyoo.Abstractions.Models.Collection", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("CollectionId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .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("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("LibraryItemId"); + + b1.ToTable("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemId") + .HasConstraintName("fk_library_items_library_items_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("LibraryItemId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("LibraryItemId"); + + b1.ToTable("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemId") + .HasConstraintName("fk_library_items_library_items_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("LibraryItemId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("LibraryItemId"); + + b1.ToTable("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemId") + .HasConstraintName("fk_library_items_library_items_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => + { + b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") + .WithMany("Movies") + .HasForeignKey("StudioID") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_movies_studios_studio_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("MovieId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("MovieId"); + + b1.ToTable("movies"); + + b1.WithOwner() + .HasForeignKey("MovieId") + .HasConstraintName("fk_movies_movies_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("MovieId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("MovieId"); + + b1.ToTable("movies"); + + b1.WithOwner() + .HasForeignKey("MovieId") + .HasConstraintName("fk_movies_movies_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("MovieId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("MovieId"); + + b1.ToTable("movies"); + + b1.WithOwner() + .HasForeignKey("MovieId") + .HasConstraintName("fk_movies_movies_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Studio"); + + 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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .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.Movie", "Movie") + .WithMany("People") + .HasForeignKey("MovieID") + .HasConstraintName("fk_people_roles_movies_movie_id"); + + 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") + .HasConstraintName("fk_people_roles_shows_show_id"); + + b.Navigation("Movie"); + + 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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .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") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .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_episode_episodes_episode_id"); + + b.HasOne("Kyoo.Abstractions.Models.User", null) + .WithMany("CurrentlyWatching") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_watched_episode_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_movie", b => + { + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_movie_collections_collection_id"); + + b.HasOne("Kyoo.Abstractions.Models.Movie", null) + .WithMany() + .HasForeignKey("movie_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_movie_movies_movie_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("Kyoo.Abstractions.Models.Movie", b => + { + b.Navigation("People"); + }); + + 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("Movies"); + + 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/20230806025743_items.cs b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs new file mode 100644 index 00000000..85b812ff --- /dev/null +++ b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs @@ -0,0 +1,64 @@ +// 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 Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Kyoo.Postgresql.Migrations +{ + /// + public partial class items : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // language=PostgreSQL + migrationBuilder.Sql(@" + CREATE VIEW library_items AS + SELECT + s.id, s.slug, s.name, s.tagline, s.aliases, s.overview, s.tags, s.genres, s.status, + s.start_air, s.end_air, s.poster_source, s.poster_blurhash, s.thumbnail_source, s.thumbnail_blurhash, + s.logo_source, s.logo_blurhash, s.trailer, s.external_id, s.start_air AS air_date, NULL as path, + 'show'::item_kind AS kind + FROM shows AS s + UNION ALL + SELECT + m.id, m.slug, m.name, m.tagline, m.aliases, m.overview, m.tags, m.genres, m.status, + m.air_date as start_air, m.air_date as end_air, m.poster_source, m.poster_blurhash, m.thumbnail_source, + m.thumbnail_blurhash, m.logo_source, m.logo_blurhash, m.trailer, m.external_id, m.air_date, m.path, + 'movie'::item_kind AS kind + FROM movies AS m + UNION ALL + SELECT + c.id, c.slug, c.name, NULL as tagline, NULL as alises, c.overview, NULL AS tags, NULL AS genres, 'unknown'::status AS status, + NULL AS start_air, NULL AS end_air, c.poster_source, c.poster_blurhash, c.thumbnail_source, + c.thumbnail_blurhash, c.logo_source, c.logo_blurhash, NULL as trailer, c.external_id, NULL AS air_date, NULL as path, + 'collection'::item_kind AS kind + FROM collections AS c + "); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + // language=PostgreSQL + migrationBuilder.Sql(@"DROP VIEW library_items"); + } + } +} diff --git a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index d41b691f..5ac1b0cd 100644 --- a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -22,28 +22,10 @@ namespace Kyoo.Postgresql.Migrations .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "action", "adventure", "animation", "comedy", "crime", "documentary", "drama", "family", "fantasy", "history", "horror", "music", "mystery", "romance", "science_fiction", "thriller", "war", "western" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_kind", new[] { "show", "movie", "collection" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("CollectionMovie", b => - { - b.Property("CollectionsId") - .HasColumnType("integer") - .HasColumnName("collections_id"); - - b.Property("MoviesId") - .HasColumnType("integer") - .HasColumnName("movies_id"); - - b.HasKey("CollectionsId", "MoviesId") - .HasName("pk_collection_movie"); - - b.HasIndex("MoviesId") - .HasDatabaseName("ix_collection_movie_movies_id"); - - b.ToTable("collection_movie", (string)null); - }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => { b.Property("Id") @@ -157,6 +139,85 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("episodes", (string)null); }); + modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AirDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("air_date"); + + b.Property("Aliases") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("EndAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_air"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("StartAir") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_air"); + + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); + + b.Property("Tagline") + .HasColumnType("text") + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); + + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); + + b.HasKey("Id") + .HasName("pk_library_items"); + + b.ToTable("library_items", (string)null); + }); + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { b.Property("Id") @@ -581,6 +642,25 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("link_user_show", (string)null); }); + modelBuilder.Entity("link_collection_movie", b => + { + b.Property("collection_id") + .HasColumnType("integer") + .HasColumnName("collection_id"); + + b.Property("movie_id") + .HasColumnType("integer") + .HasColumnName("movie_id"); + + b.HasKey("collection_id", "movie_id") + .HasName("pk_link_collection_movie"); + + b.HasIndex("movie_id") + .HasDatabaseName("ix_link_collection_movie_movie_id"); + + b.ToTable("link_collection_movie", (string)null); + }); + modelBuilder.Entity("link_collection_show", b => { b.Property("collection_id") @@ -600,23 +680,6 @@ namespace Kyoo.Postgresql.Migrations b.ToTable("link_collection_show", (string)null); }); - modelBuilder.Entity("CollectionMovie", b => - { - b.HasOne("Kyoo.Abstractions.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_collection_movie_collections_collections_id"); - - b.HasOne("Kyoo.Abstractions.Models.Movie", null) - .WithMany() - .HasForeignKey("MoviesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_collection_movie_movies_movies_id"); - }); - modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => { b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => @@ -808,6 +871,93 @@ namespace Kyoo.Postgresql.Migrations b.Navigation("Thumbnail"); }); + modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("LibraryItemId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("LibraryItemId"); + + b1.ToTable("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemId") + .HasConstraintName("fk_library_items_library_items_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("LibraryItemId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("LibraryItemId"); + + b1.ToTable("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemId") + .HasConstraintName("fk_library_items_library_items_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("LibraryItemId") + .HasColumnType("integer") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("LibraryItemId"); + + b1.ToTable("library_items"); + + b1.WithOwner() + .HasForeignKey("LibraryItemId") + .HasConstraintName("fk_library_items_library_items_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Thumbnail"); + }); + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => { b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") @@ -1274,6 +1424,23 @@ namespace Kyoo.Postgresql.Migrations .HasConstraintName("fk_link_user_show_shows_watched_id"); }); + modelBuilder.Entity("link_collection_movie", b => + { + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_movie_collections_collection_id"); + + b.HasOne("Kyoo.Abstractions.Models.Movie", null) + .WithMany() + .HasForeignKey("movie_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_movie_movies_movie_id"); + }); + modelBuilder.Entity("link_collection_show", b => { b.HasOne("Kyoo.Abstractions.Models.Collection", null) diff --git a/back/src/Kyoo.Postgresql/PostgresContext.cs b/back/src/Kyoo.Postgresql/PostgresContext.cs index dc2918ea..71ec5117 100644 --- a/back/src/Kyoo.Postgresql/PostgresContext.cs +++ b/back/src/Kyoo.Postgresql/PostgresContext.cs @@ -49,6 +49,7 @@ namespace Kyoo.Postgresql { NpgsqlConnection.GlobalTypeMapper.MapEnum(); NpgsqlConnection.GlobalTypeMapper.MapEnum(); + NpgsqlConnection.GlobalTypeMapper.MapEnum(); } /// @@ -101,6 +102,7 @@ namespace Kyoo.Postgresql { modelBuilder.HasPostgresEnum(); modelBuilder.HasPostgresEnum(); + modelBuilder.HasPostgresEnum(); base.OnModelCreating(modelBuilder); } diff --git a/shell.nix b/shell.nix index 877ae085..970e7db1 100644 --- a/shell.nix +++ b/shell.nix @@ -24,7 +24,7 @@ in openssl mediainfo ffmpeg - postgresql + postgresql_15 ]; RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; From a6c8105d8cd4b62560770cb74408551c611c9bbd Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 6 Aug 2023 13:17:35 +0900 Subject: [PATCH 05/37] Parse images from the scanner --- back/src/Directory.Build.props | 2 +- .../Resources/Interfaces/IThumbnails.cs | 33 +++++++++++++++++-- .../Models/Utils/Identifier.cs | 11 +++---- .../Controllers/ThumbnailsManager.cs | 5 +-- back/src/Kyoo.Core/Kyoo.Core.csproj | 1 + .../Kyoo.Core/Views/Helper/CrudThumbsApi.cs | 2 +- scanner/providers/types/episode.py | 4 --- scanner/providers/types/movie.py | 6 ++-- scanner/providers/types/season.py | 5 ++- scanner/providers/types/show.py | 6 ++-- scanner/providers/types/studio.py | 2 +- 11 files changed, 51 insertions(+), 26 deletions(-) diff --git a/back/src/Directory.Build.props b/back/src/Directory.Build.props index c0c382c2..6918ac14 100644 --- a/back/src/Directory.Build.props +++ b/back/src/Directory.Build.props @@ -46,7 +46,7 @@ $(MSBuildThisFileDirectory)../Kyoo.ruleset - 1591 + 1591;1305 diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs index 10e1b742..11dfd6c4 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs @@ -16,7 +16,10 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . +using System; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Globalization; namespace Kyoo.Abstractions.Models { @@ -42,6 +45,7 @@ namespace Kyoo.Abstractions.Models public Image? Logo { get; set; } } + [TypeConverter(typeof(ImageConvertor))] public class Image { /// @@ -54,6 +58,31 @@ namespace Kyoo.Abstractions.Models /// [MaxLength(32)] public string Blurhash { get; set; } + + public Image(string source, string? blurhash = null) + { + Source = source; + Blurhash = blurhash ?? "00000000000000"; + } + + public class ImageConvertor : TypeConverter + { + /// + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + return base.CanConvertFrom(context, sourceType); + } + + /// + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is not string source) + return base.ConvertFrom(context, culture, value)!; + return new Image(source); + } + } } /// @@ -64,7 +93,7 @@ namespace Kyoo.Abstractions.Models /// /// Small /// - Small, + Low, /// /// Medium @@ -74,6 +103,6 @@ namespace Kyoo.Abstractions.Models /// /// Large /// - Large, + High, } } diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs index 2bbcfc5b..ae514d5c 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs @@ -23,7 +23,6 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using JetBrains.Annotations; namespace Kyoo.Abstractions.Models.Utils { @@ -43,7 +42,7 @@ namespace Kyoo.Abstractions.Models.Utils /// /// The slug of the resource or null if the id is specified. /// - private readonly string _slug; + private readonly string? _slug; /// /// Create a new for the given id. @@ -183,7 +182,7 @@ namespace Kyoo.Abstractions.Models.Utils { return _id.HasValue ? _id.Value.ToString() - : _slug; + : _slug!; } /// @@ -192,7 +191,7 @@ namespace Kyoo.Abstractions.Models.Utils public class IdentifierConvertor : TypeConverter { /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { if (sourceType == typeof(int) || sourceType == typeof(string)) return true; @@ -200,12 +199,12 @@ namespace Kyoo.Abstractions.Models.Utils } /// - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { if (value is int id) return new Identifier(id); if (value is not string slug) - return base.ConvertFrom(context, culture, value); + return base.ConvertFrom(context, culture, value)!; return int.TryParse(slug, out id) ? new Identifier(id) : new Identifier(slug); diff --git a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs index 652380d2..b566c195 100644 --- a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -20,6 +20,7 @@ using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; +using BlurHashSharp.SkiaSharp; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Microsoft.Extensions.Logging; @@ -76,13 +77,13 @@ namespace Kyoo.Core.Controllers SKBitmap bitmap = SKBitmap.Decode(reader); bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.High); - await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Large.ToString().ToLowerInvariant()}.jpg"); + await _WriteTo(bitmap, $"{localPath}.{ImageQuality.High.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"); + await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Low.ToString().ToLowerInvariant()}.jpg"); } catch (Exception ex) { diff --git a/back/src/Kyoo.Core/Kyoo.Core.csproj b/back/src/Kyoo.Core/Kyoo.Core.csproj index 0839dfe5..332e4b6a 100644 --- a/back/src/Kyoo.Core/Kyoo.Core.csproj +++ b/back/src/Kyoo.Core/Kyoo.Core.csproj @@ -11,6 +11,7 @@ + diff --git a/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs b/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs index e2c9d6c4..9e6c95f5 100644 --- a/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs +++ b/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs @@ -65,7 +65,7 @@ namespace Kyoo.Core.Api ); if (resource == null) return NotFound(); - string path = _thumbs.GetImagePath(resource, image, quality ?? ImageQuality.Large); + string path = _thumbs.GetImagePath(resource, image, quality ?? ImageQuality.High); if (path == null || !System.IO.File.Exists(path)) return NotFound(); return PhysicalFile(Path.GetFullPath(path), "image/jpeg", true); diff --git a/scanner/providers/types/episode.py b/scanner/providers/types/episode.py index e374e0a7..f46eb488 100644 --- a/scanner/providers/types/episode.py +++ b/scanner/providers/types/episode.py @@ -40,9 +40,5 @@ class Episode: return { **asdict(self), **asdict(self.translations[default_language]), - # "poster": next(iter(self.translations[default_language].posters), None), - # "thumbnail": next(iter(self.translations[default_language].thumbnails), None), - # "logo": next(iter(self.translations[default_language].logos), None), - "thumbnail": None, "show": None, } diff --git a/scanner/providers/types/movie.py b/scanner/providers/types/movie.py index 1f70b8e3..64bcd9b5 100644 --- a/scanner/providers/types/movie.py +++ b/scanner/providers/types/movie.py @@ -50,9 +50,9 @@ class Movie: return { **asdict(self), **asdict(self.translations[default_language]), - # "poster": next(iter(self.translations[default_language].posters), None), - # "thumbnail": next(iter(self.translations[default_language].thumbnails), None), - # "logo": next(iter(self.translations[default_language].logos), None), + "poster": next(iter(self.translations[default_language].posters), None), + "thumbnail": next(iter(self.translations[default_language].thumbnails), None), + "logo": next(iter(self.translations[default_language].logos), None), "trailer": next(iter(self.translations[default_language].trailers), None), "studio": next((x.to_kyoo() for x in self.studios), None), "genres": [x.to_kyoo() for x in self.genres], diff --git a/scanner/providers/types/season.py b/scanner/providers/types/season.py index c234364e..0acfa350 100644 --- a/scanner/providers/types/season.py +++ b/scanner/providers/types/season.py @@ -30,7 +30,6 @@ class Season: return { **asdict(self), **asdict(self.translations[default_language]), - # "poster": next(iter(self.translations[default_language].posters), None), - # "thumbnail": next(iter(self.translations[default_language].thumbnails), None), - # "logo": next(iter(self.translations[default_language].logos), None), + "poster": next(iter(self.translations[default_language].posters), None), + "thumbnail": next(iter(self.translations[default_language].thumbnails), None), } diff --git a/scanner/providers/types/show.py b/scanner/providers/types/show.py index 069eff3e..306a77ec 100644 --- a/scanner/providers/types/show.py +++ b/scanner/providers/types/show.py @@ -54,9 +54,9 @@ class Show: **asdict(self.translations[default_language]), "studio": next((x.to_kyoo() for x in self.studios), None), "seasons": None, - # "poster": next(iter(self.translations[default_language].posters), None), - # "thumbnail": next(iter(self.translations[default_language].thumbnails), None), - # "logo": next(iter(self.translations[default_language].logos), None), + "poster": next(iter(self.translations[default_language].posters), None), + "thumbnail": next(iter(self.translations[default_language].thumbnails), None), + "logo": next(iter(self.translations[default_language].logos), None), "trailer": next(iter(self.translations[default_language].trailers), None), "genres": [x.to_kyoo() for x in self.genres], } diff --git a/scanner/providers/types/studio.py b/scanner/providers/types/studio.py index 756332a3..0ed11f6a 100644 --- a/scanner/providers/types/studio.py +++ b/scanner/providers/types/studio.py @@ -12,5 +12,5 @@ class Studio: def to_kyoo(self): return { **asdict(self), - # "logo": next(iter(self.logos), None), + "logo": next(iter(self.logos), None), } From 3cdfc91c934867456705c0f5ef1e53f21589a256 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 7 Aug 2023 01:01:16 +0900 Subject: [PATCH 06/37] Implement blurhash --- .../Resources/Interfaces/IThumbnails.cs | 8 +++- .../Repositories/LocalRepository.cs | 18 +++++++- .../Repositories/UserRepository.cs | 2 + .../Controllers/ThumbnailsManager.cs | 25 ++++++---- back/src/Kyoo.Core/Kyoo.Core.csproj | 2 +- .../Kyoo.Core/Views/Helper/CrudThumbsApi.cs | 6 +-- .../Helper/Serializers/ImageConvertor.cs | 46 ------------------- .../Serializers/JsonSerializerContract.cs | 39 ++++++++-------- 8 files changed, 62 insertions(+), 84 deletions(-) delete mode 100644 back/src/Kyoo.Core/Views/Helper/Serializers/ImageConvertor.cs diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs index 11dfd6c4..e3454fc1 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs @@ -29,7 +29,7 @@ namespace Kyoo.Abstractions.Models public interface IThumbnails { /// - /// A poster is a 9/16 format image with the cover of the resource. + /// A poster is a 2/3 format image with the cover of the resource. /// public Image? Poster { get; set; } @@ -82,6 +82,12 @@ namespace Kyoo.Abstractions.Models return base.ConvertFrom(context, culture, value)!; return new Image(source); } + + /// + public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) + { + return false; + } } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 6d33283e..8e421c7c 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -341,8 +341,15 @@ namespace Kyoo.Core.Controllers if (obj == null) throw new ArgumentNullException(nameof(obj)); await Validate(obj); - // if (obj is IThumbnails thumbs) - // await _thumbnailsManager.DownloadImages(thumbs); + if (obj is IThumbnails thumbs) + { + if (thumbs.Poster != null) + Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Added; + if (thumbs.Thumbnail != null) + Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Added; + if (thumbs.Logo != null) + Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Added; + } return obj; } @@ -427,6 +434,13 @@ namespace Kyoo.Core.Controllers /// A representing the asynchronous operation. protected virtual Task EditRelations(T resource, T changed, bool resetOld) { + if (resource is IThumbnails thumbs) + { + // FIXME: I think this throws if poster is set to null. + Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Modified; + Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Modified; + Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Modified; + } return Validate(resource); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs index 89c00fdd..bf6f3e5e 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs @@ -66,6 +66,8 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; + if (obj.Logo != null) + _database.Entry(obj).Reference(x => x.Logo).TargetEntry.State = EntityState.Added; await _database.SaveChangesAsync(() => Get(obj.Slug)); OnResourceCreated(obj); return obj; diff --git a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs index b566c195..d11fd898 100644 --- a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -20,7 +20,7 @@ using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; -using BlurHashSharp.SkiaSharp; +using Blurhash.SkiaSharp; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Microsoft.Extensions.Logging; @@ -62,28 +62,33 @@ namespace Kyoo.Core.Controllers await reader.CopyToAsync(file); } - private async Task _DownloadImage(string? url, string localPath, string what) + private async Task _DownloadImage(Image? image, string localPath, string what) { - if (url == null) + if (image == null) return; try { _logger.LogInformation("Downloading image {What}", what); HttpClient client = _clientFactory.CreateClient(); - HttpResponseMessage response = await client.GetAsync(url); + HttpResponseMessage response = await client.GetAsync(image.Source); response.EnsureSuccessStatusCode(); await using Stream reader = await response.Content.ReadAsStreamAsync(); - SKBitmap bitmap = SKBitmap.Decode(reader); + using SKCodec codec = SKCodec.Create(reader); + SKImageInfo info = codec.Info; + info.ColorType = SKColorType.Rgba8888; + SKBitmap bitmap = SKBitmap.Decode(codec, info); bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.High); await _WriteTo(bitmap, $"{localPath}.{ImageQuality.High.ToString().ToLowerInvariant()}.jpg"); - bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.Medium); + bitmap.Resize(new SKSizeI((int)(bitmap.Width / 1.5), (int)(bitmap.Height / 1.5)), SKFilterQuality.Medium); await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Medium.ToString().ToLowerInvariant()}.jpg"); - bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.Low); + bitmap.Resize(new SKSizeI(bitmap.Width / 2, bitmap.Height / 2), SKFilterQuality.Low); await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Low.ToString().ToLowerInvariant()}.jpg"); + + image.Blurhash = Blurhasher.Encode(bitmap, 4, 3); } catch (Exception ex) { @@ -99,9 +104,9 @@ namespace Kyoo.Core.Controllers throw new ArgumentNullException(nameof(item)); string name = item is IResource res ? res.Slug : "???"; - 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}"); + await _DownloadImage(item.Poster, _GetBaseImagePath(item, "poster"), $"The poster of {name}"); + await _DownloadImage(item.Thumbnail, _GetBaseImagePath(item, "thumbnail"), $"The poster of {name}"); + await _DownloadImage(item.Logo, _GetBaseImagePath(item, "logo"), $"The poster of {name}"); } private static string _GetBaseImagePath(T item, string image) diff --git a/back/src/Kyoo.Core/Kyoo.Core.csproj b/back/src/Kyoo.Core/Kyoo.Core.csproj index 332e4b6a..b4061a1c 100644 --- a/back/src/Kyoo.Core/Kyoo.Core.csproj +++ b/back/src/Kyoo.Core/Kyoo.Core.csproj @@ -6,7 +6,7 @@ - + diff --git a/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs b/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs index 9e6c95f5..d02cb624 100644 --- a/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs +++ b/back/src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs @@ -135,10 +135,8 @@ namespace Kyoo.Core.Api /// public override async Task> Create([FromBody] T resource) { - // TODO: Remove this method and use a websocket API to do that. - ActionResult ret = await base.Create(resource); - await _thumbs.DownloadImages(ret.Value); - return ret; + await _thumbs.DownloadImages(resource); + return await base.Create(resource); } } } diff --git a/back/src/Kyoo.Core/Views/Helper/Serializers/ImageConvertor.cs b/back/src/Kyoo.Core/Views/Helper/Serializers/ImageConvertor.cs deleted file mode 100644 index 52674995..00000000 --- a/back/src/Kyoo.Core/Views/Helper/Serializers/ImageConvertor.cs +++ /dev/null @@ -1,46 +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 Kyoo.Abstractions.Models; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Kyoo.Core.Api -{ - public class ImageConverter : JsonConverter - { - /// - public override void WriteJson(JsonWriter writer, Image value, JsonSerializer serializer) - { - JObject obj = JObject.FromObject(value, serializer); - obj.WriteTo(writer); - } - - /// - public override Image ReadJson(JsonReader reader, - Type objectType, - Image existingValue, - bool hasExistingValue, - JsonSerializer serializer) - { - throw new NotImplementedException(); - } - } -} diff --git a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs index 98429630..e8cacb82 100644 --- a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs +++ b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs @@ -78,29 +78,28 @@ namespace Kyoo.Core.Api protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { IList properties = base.CreateProperties(type, memberSerialization); - if (!type.IsAssignableTo(typeof(Image))) - return properties; + // 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) - // }); + // 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; } From ab12de82872e3c9f4a8d1bd69f5dc3078cba45d4 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 7 Aug 2023 12:36:23 +0900 Subject: [PATCH 07/37] Add next/previous episode field. Add movie load --- .../Models/Resources/Episode.cs | 19 +++++ .../Models/Resources/Movie.cs | 9 +++ .../Kyoo.Core/Controllers/LibraryManager.cs | 39 ++++++++++ .../Repositories/LibraryItemRepository.cs | 2 - .../Serializers/JsonSerializerContract.cs | 72 ------------------- back/src/Kyoo.Postgresql/DatabaseContext.cs | 4 ++ 6 files changed, 71 insertions(+), 74 deletions(-) diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index a226c8a8..eda05306 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -150,6 +150,25 @@ namespace Kyoo.Abstractions.Models /// public Dictionary ExternalId { get; set; } = new(); + /// + /// The previous episode that should be seen before viewing this one. + /// + [LoadableRelation] public Episode? PreviousEpisode { get; set; } + + /// + /// The next episode to watch after this one. + /// + [LoadableRelation] public Episode? NextEpisode { get; set; } + + /// + /// Links to watch this episode. + /// + public object Links => new + { + Direct = $"/video/episode/{Slug}/direct", + Hls = $"/video/episode/{Slug}/master.m3u8", + }; + /// /// Get the slug of an episode. /// diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index 99a9f854..de9b6243 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -121,6 +121,15 @@ namespace Kyoo.Abstractions.Models /// [LoadableRelation] public ICollection? Collections { get; set; } + /// + /// Links to watch this movie. + /// + public object Links => new + { + Direct = $"/video/movie/{Slug}/direct", + Hls = $"/video/movie/{Slug}/master.m3u8", + }; + /// public void OnMerge(object merged) { diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs index d2c4d172..b134e76b 100644 --- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -255,6 +255,28 @@ namespace Kyoo.Core.Controllers .GetAll(x => x.Collections.Any(y => y.Id == obj.Id)) .Then(x => c.Shows = x), + (Collection c, nameof(Collection.Movies)) => MovieRepository + .GetAll(x => x.Collections.Any(y => y.Id == obj.Id)) + .Then(x => c.Movies = x), + + + (Movie m, nameof(Movie.People)) => PeopleRepository + .GetFromShow(obj.Id) + .Then(x => m.People = x), + + (Movie m, nameof(Movie.Collections)) => CollectionRepository + .GetAll(x => x.Movies.Any(y => y.Id == obj.Id)) + .Then(x => m.Collections = x), + + (Movie m, nameof(Movie.Studio)) => StudioRepository + .GetOrDefault(x => x.Movies.Any(y => y.Id == obj.Id)) + .Then(x => + { + m.Studio = x; + m.StudioID = x?.Id ?? 0; + }), + + (Show s, nameof(Show.People)) => PeopleRepository .GetFromShow(obj.Id) .Then(x => s.People = x), @@ -312,11 +334,28 @@ namespace Kyoo.Core.Controllers e.SeasonID = x?.Id ?? 0; }), + (Episode e, nameof(Episode.PreviousEpisode)) => EpisodeRepository + .GetAll( + where: x => x.ShowID == e.ShowID, + limit: new Pagination(1, e.Id, true) + ).Then(x => e.PreviousEpisode = x.FirstOrDefault()), + + (Episode e, nameof(Episode.NextEpisode)) => EpisodeRepository + .GetAll( + where: x => x.ShowID == e.ShowID, + limit: new Pagination(1, e.Id) + ).Then(x => e.NextEpisode = x.FirstOrDefault()), + (Studio s, nameof(Studio.Shows)) => ShowRepository .GetAll(x => x.Studio.Id == obj.Id) .Then(x => s.Shows = x), + (Studio s, nameof(Studio.Movies)) => MovieRepository + .GetAll(x => x.Studio.Id == obj.Id) + .Then(x => s.Movies = x), + + (People p, nameof(People.Roles)) => PeopleRepository .GetFromPeople(obj.Id) .Then(x => p.Roles = x), diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index 3414b63c..33dd9b8b 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -69,8 +69,6 @@ namespace Kyoo.Core.Controllers Pagination limit = default) { return await ApplyFilters(_database.LibraryItems, where, sort, limit); - // .Select(x => x.ToItem()) - // .ToList(); } /// diff --git a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs index e8cacb82..a5daeec4 100644 --- a/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs +++ b/back/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.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.Models; @@ -73,76 +72,5 @@ namespace Kyoo.Core.Api property.ShouldDeserialize = _ => false; return property; } - - /// - protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) - { - IList properties = base.CreateProperties(type, memberSerialization); - // 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) - // }); - // } - return properties; - } - - /// - /// A custom that uses the - /// . as a value. - /// - private class ThumbnailProvider : IValueProvider - { - /// - /// The index/ID of the image to retrieve/set. - /// - private readonly int _imageIndex; - - /// - /// Create a new . - /// - /// The index/ID of the image to retrieve/set. - public ThumbnailProvider(int imageIndex) - { - _imageIndex = imageIndex; - } - - /// - public void SetValue(object target, object value) - { - throw new NotSupportedException(); - } - - /// - public object GetValue(object target) - { - 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 7982c6e6..3d794f2c 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -231,6 +231,10 @@ namespace Kyoo.Postgresql { base.OnModelCreating(modelBuilder); + modelBuilder.Entity() + .Ignore(x => x.PreviousEpisode) + .Ignore(x => x.NextEpisode); + modelBuilder.Entity() .Ignore(x => x.ForPeople); From 93b36f1bd4dfcc4263b448367834b888ee0bca3d Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 7 Aug 2023 13:02:49 +0900 Subject: [PATCH 08/37] Fix image resize not working --- .../Kyoo.Core/Controllers/ThumbnailsManager.cs | 16 ++++++++-------- .../Migrations/20230806025743_items.cs | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs index d11fd898..3c276145 100644 --- a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -77,18 +77,18 @@ namespace Kyoo.Core.Controllers using SKCodec codec = SKCodec.Create(reader); SKImageInfo info = codec.Info; info.ColorType = SKColorType.Rgba8888; - SKBitmap bitmap = SKBitmap.Decode(codec, info); + using SKBitmap original = SKBitmap.Decode(codec, info); - bitmap.Resize(new SKSizeI(bitmap.Width, bitmap.Height), SKFilterQuality.High); - await _WriteTo(bitmap, $"{localPath}.{ImageQuality.High.ToString().ToLowerInvariant()}.jpg"); + using SKBitmap high = original.Resize(new SKSizeI(original.Width, original.Height), SKFilterQuality.High); + await _WriteTo(high, $"{localPath}.{ImageQuality.High.ToString().ToLowerInvariant()}.jpg"); - bitmap.Resize(new SKSizeI((int)(bitmap.Width / 1.5), (int)(bitmap.Height / 1.5)), SKFilterQuality.Medium); - await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Medium.ToString().ToLowerInvariant()}.jpg"); + using SKBitmap medium = high.Resize(new SKSizeI((int)(high.Width / 1.5), (int)(high.Height / 1.5)), SKFilterQuality.Medium); + await _WriteTo(medium, $"{localPath}.{ImageQuality.Medium.ToString().ToLowerInvariant()}.jpg"); - bitmap.Resize(new SKSizeI(bitmap.Width / 2, bitmap.Height / 2), SKFilterQuality.Low); - await _WriteTo(bitmap, $"{localPath}.{ImageQuality.Low.ToString().ToLowerInvariant()}.jpg"); + using SKBitmap low = medium.Resize(new SKSizeI(medium.Width / 2, medium.Height / 2), SKFilterQuality.Low); + await _WriteTo(low, $"{localPath}.{ImageQuality.Low.ToString().ToLowerInvariant()}.jpg"); - image.Blurhash = Blurhasher.Encode(bitmap, 4, 3); + image.Blurhash = Blurhasher.Encode(low, 4, 3); } catch (Exception ex) { diff --git a/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs index 85b812ff..278b0179 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs @@ -39,14 +39,14 @@ namespace Kyoo.Postgresql.Migrations FROM shows AS s UNION ALL SELECT - m.id, m.slug, m.name, m.tagline, m.aliases, m.overview, m.tags, m.genres, m.status, + -m.id, m.slug, m.name, m.tagline, m.aliases, m.overview, m.tags, m.genres, m.status, m.air_date as start_air, m.air_date as end_air, m.poster_source, m.poster_blurhash, m.thumbnail_source, m.thumbnail_blurhash, m.logo_source, m.logo_blurhash, m.trailer, m.external_id, m.air_date, m.path, 'movie'::item_kind AS kind FROM movies AS m UNION ALL SELECT - c.id, c.slug, c.name, NULL as tagline, NULL as alises, c.overview, NULL AS tags, NULL AS genres, 'unknown'::status AS status, + c.id + 10000 AS id, c.slug, c.name, NULL as tagline, NULL as alises, c.overview, NULL AS tags, NULL AS genres, 'unknown'::status AS status, NULL AS start_air, NULL AS end_air, c.poster_source, c.poster_blurhash, c.thumbnail_source, c.thumbnail_blurhash, c.logo_source, c.logo_blurhash, NULL as trailer, c.external_id, NULL AS air_date, NULL as path, 'collection'::item_kind AS kind From 5446dbce83ecc49389d892c780eb3b788e5cb6fb Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 7 Aug 2023 14:49:42 +0900 Subject: [PATCH 09/37] Fix tests compilation errors --- .../Controllers/ILibraryManager.cs | 16 +- .../Controllers/IRepository.cs | 17 +- .../Models/PartialResource.cs} | 30 +- .../Models/Resources/Episode.cs | 10 +- .../Models/Resources/Season.cs | 6 +- .../Models/Resources/Show.cs | 10 +- .../Utility/EnumerableExtensions.cs | 220 ----------- back/src/Kyoo.Abstractions/Utility/Merger.cs | 225 +---------- .../Kyoo.Abstractions/Utility/TaskUtils.cs | 33 -- back/src/Kyoo.Abstractions/Utility/Utility.cs | 30 +- back/src/Kyoo.Authentication/Views/AuthApi.cs | 10 +- .../Kyoo.Core/Controllers/LibraryManager.cs | 29 +- .../Repositories/EpisodeRepository.cs | 22 +- .../Repositories/LibraryItemRepository.cs | 6 +- .../Repositories/LocalRepository.cs | 76 ++-- .../Repositories/SeasonRepository.cs | 14 +- .../Repositories/ShowRepository.cs | 2 +- back/src/Kyoo.Core/Views/Helper/CrudApi.cs | 17 +- .../src/Kyoo.Core/Views/Metadata/StudioApi.cs | 2 +- .../Kyoo.Core/Views/Resources/SeasonApi.cs | 2 +- back/src/Kyoo.Core/Views/Resources/ShowApi.cs | 4 +- back/src/Kyoo.Postgresql/DatabaseContext.cs | 4 +- .../Kyoo.Tests/Database/RepositoryTests.cs | 14 +- .../SpecificTests/CollectionsTests.cs | 46 +-- .../Database/SpecificTests/EpisodeTests.cs | 110 ++---- .../Database/SpecificTests/LibraryItemTest.cs | 98 ----- .../Database/SpecificTests/LibraryTests.cs | 169 -------- .../Database/SpecificTests/PeopleTests.cs | 51 +-- .../Database/SpecificTests/ProviderTests.cs | 48 --- .../Database/SpecificTests/SeasonTests.cs | 62 ++- .../Database/SpecificTests/ShowTests.cs | 126 ++---- back/tests/Kyoo.Tests/Database/TestSample.cs | 186 ++------- .../Kyoo.Tests/Utility/EnumerableTests.cs | 49 --- back/tests/Kyoo.Tests/Utility/MergerTests.cs | 369 +----------------- back/tests/Kyoo.Tests/Utility/TaskTests.cs | 39 -- back/tests/Kyoo.Tests/Utility/UtilityTests.cs | 14 - 36 files changed, 326 insertions(+), 1840 deletions(-) rename back/{tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs => src/Kyoo.Abstractions/Models/PartialResource.cs} (51%) delete mode 100644 back/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs delete mode 100644 back/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs delete mode 100644 back/tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs index 646f44ca..91e0064f 100644 --- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -389,11 +389,23 @@ namespace Kyoo.Abstractions.Controllers /// Edit a resource /// /// The resource to edit, it's ID can't change. - /// Should old properties of the resource be discarded or should null values considered as not changed? /// The type of resources /// If the item is not found /// The resource edited and completed by database's information (related items and so on) - Task Edit(T item, bool resetOld) + Task Edit(T item) + where T : class, IResource; + + /// + /// Edit only specific properties of a resource + /// + /// The id of the resource to edit + /// + /// A method that will be called when you need to update every properties that you want to + /// persist. It can return false to abort the process via an ArgumentException + /// + /// If the item is not found + /// The resource edited and completed by database's information (related items and so on) + Task Patch(int id, Func> patch) where T : class, IResource; /// diff --git a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs index ead00dbf..fb00b768 100644 --- a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs +++ b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs @@ -129,13 +129,24 @@ namespace Kyoo.Abstractions.Controllers event ResourceEventHandler OnCreated; /// - /// Edit a resource + /// Edit a resource and replace every property /// /// The resource to edit, it's ID can't change. - /// Should old properties of the resource be discarded or should null values considered as not changed? /// If the item is not found /// The resource edited and completed by database's information (related items and so on) - Task Edit(T edited, bool resetOld); + Task Edit(T edited); + + /// + /// Edit only specific properties of a resource + /// + /// The id of the resource to edit + /// + /// A method that will be called when you need to update every properties that you want to + /// persist. It can return false to abort the process via an ArgumentException + /// + /// If the item is not found + /// The resource edited and completed by database's information (related items and so on) + Task Patch(int id, Func> patch); /// /// Called when a resource has been edited. diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs b/back/src/Kyoo.Abstractions/Models/PartialResource.cs similarity index 51% rename from back/tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs rename to back/src/Kyoo.Abstractions/Models/PartialResource.cs index b4456756..7e9b0089 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/GenreTests.cs +++ b/back/src/Kyoo.Abstractions/Models/PartialResource.cs @@ -16,33 +16,11 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System.Diagnostics.CodeAnalysis; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Xunit; -using Xunit.Abstractions; +namespace Kyoo.Models; -namespace Kyoo.Tests.Database +public class PartialResource { - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class GenreTests : AGenreTests - { - public GenreTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } + public int? Id { get; set; } - public abstract class AGenreTests : RepositoryTests - { - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private readonly IGenreRepository _repository; - - protected AGenreTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.GenreRepository; - } - } + public string? Slug { get; set; } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index eda05306..786f2572 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -43,7 +43,7 @@ namespace Kyoo.Abstractions.Models { if (ShowSlug != null || Show?.Slug != null) return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); - return GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber); + return GetSlug(ShowId.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber); } [UsedImplicitly] @@ -81,17 +81,17 @@ namespace Kyoo.Abstractions.Models /// /// The ID of the Show containing this episode. /// - [SerializeIgnore] public int ShowID { get; set; } + [SerializeIgnore] public int ShowId { get; set; } /// /// The show that contains this episode. This must be explicitly loaded via a call to . /// - [LoadableRelation(nameof(ShowID))] public Show? Show { get; set; } + [LoadableRelation(nameof(ShowId))] public Show? Show { get; set; } /// /// The ID of the Season containing this episode. /// - [SerializeIgnore] public int? SeasonID { get; set; } + [SerializeIgnore] public int? SeasonId { get; set; } /// /// The season that contains this episode. @@ -101,7 +101,7 @@ namespace Kyoo.Abstractions.Models /// This can be null if the season is unknown and the episode is only identified /// by it's . /// - [LoadableRelation(nameof(SeasonID))] public Season? Season { get; set; } + [LoadableRelation(nameof(SeasonId))] public Season? Season { get; set; } /// /// The season in witch this episode is in. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs index b1eacec4..6cd21c3c 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs @@ -42,7 +42,7 @@ namespace Kyoo.Abstractions.Models get { if (ShowSlug == null && Show == null) - return $"{ShowID}-s{SeasonNumber}"; + return $"{ShowId}-s{SeasonNumber}"; return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}"; } @@ -67,13 +67,13 @@ namespace Kyoo.Abstractions.Models /// /// The ID of the Show containing this season. /// - [SerializeIgnore] public int ShowID { get; set; } + [SerializeIgnore] public int ShowId { get; set; } /// /// The show that contains this season. /// This must be explicitly loaded via a call to . /// - [LoadableRelation(nameof(ShowID))] public Show? Show { get; set; } + [LoadableRelation(nameof(ShowId))] public Show? Show { get; set; } /// /// The number of this season. This can be set to 0 to indicate specials. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs index 38a4a42e..aeba5c68 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -51,7 +51,7 @@ namespace Kyoo.Abstractions.Models /// /// The list of alternative titles of this show. /// - public string[] Aliases { get; set; } = Array.Empty(); + public List Aliases { get; set; } = new(); /// /// The summary of this show. @@ -61,12 +61,12 @@ namespace Kyoo.Abstractions.Models /// /// A list of tags that match this movie. /// - public string[] Tags { get; set; } = Array.Empty(); + public List Tags { get; set; } = new(); /// /// The list of genres (themes) this show has. /// - public Genre[] Genres { get; set; } = Array.Empty(); + public List Genres { get; set; } = new(); /// /// Is this show airing, not aired yet or finished? @@ -106,13 +106,13 @@ namespace Kyoo.Abstractions.Models /// /// The ID of the Studio that made this show. /// - [SerializeIgnore] public int? StudioID { get; set; } + [SerializeIgnore] public int? StudioId { get; set; } /// /// The Studio that made this show. /// This must be explicitly loaded via a call to . /// - [LoadableRelation(nameof(StudioID))][EditableRelation] public Studio? Studio { get; set; } + [LoadableRelation(nameof(StudioId))][EditableRelation] public Studio? Studio { get; set; } /// /// The list of people that made this show. diff --git a/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs b/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs index fd1e4f4d..5d3d9038 100644 --- a/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs +++ b/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs @@ -17,9 +17,7 @@ // along with Kyoo. If not, see . using System; -using System.Collections; using System.Collections.Generic; -using System.Threading.Tasks; using JetBrains.Annotations; namespace Kyoo.Utils @@ -29,125 +27,6 @@ namespace Kyoo.Utils /// public static class EnumerableExtensions { - /// - /// A Select where the index of the item can be used. - /// - /// The IEnumerable to map. If self is null, an empty list is returned - /// The function that will map each items - /// The type of items in - /// The type of items in the returned list - /// The list mapped. - /// The list or the mapper can't be null - [LinqTunnel] - public static IEnumerable Map(this IEnumerable self, - Func mapper) - { - if (self == null) - throw new ArgumentNullException(nameof(self)); - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); - - static IEnumerable Generator(IEnumerable self, Func mapper) - { - using IEnumerator enumerator = self.GetEnumerator(); - int index = 0; - - while (enumerator.MoveNext()) - { - yield return mapper(enumerator.Current, index); - index++; - } - } - return Generator(self, mapper); - } - - /// - /// A map where the mapping function is asynchronous. - /// Note: might interest you. - /// - /// The IEnumerable to map. - /// The asynchronous function that will map each items. - /// The type of items in . - /// The type of items in the returned list. - /// The list mapped as an AsyncEnumerable. - /// The list or the mapper can't be null. - [LinqTunnel] - public static IAsyncEnumerable MapAsync(this IEnumerable self, - Func> mapper) - { - if (self == null) - throw new ArgumentNullException(nameof(self)); - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); - - static async IAsyncEnumerable Generator(IEnumerable self, Func> mapper) - { - using IEnumerator enumerator = self.GetEnumerator(); - int index = 0; - - while (enumerator.MoveNext()) - { - yield return await mapper(enumerator.Current, index); - index++; - } - } - - return Generator(self, mapper); - } - - /// - /// An asynchronous version of Select. - /// - /// The IEnumerable to map - /// The asynchronous function that will map each items - /// The type of items in - /// The type of items in the returned list - /// The list mapped as an AsyncEnumerable - /// The list or the mapper can't be null - [LinqTunnel] - public static IAsyncEnumerable SelectAsync(this IEnumerable self, - Func> mapper) - { - if (self == null) - throw new ArgumentNullException(nameof(self)); - if (mapper == null) - throw new ArgumentNullException(nameof(mapper)); - - static async IAsyncEnumerable Generator(IEnumerable self, Func> mapper) - { - using IEnumerator enumerator = self.GetEnumerator(); - - while (enumerator.MoveNext()) - yield return await mapper(enumerator.Current); - } - - return Generator(self, mapper); - } - - /// - /// Convert an AsyncEnumerable to a List by waiting for every item. - /// - /// The async list - /// The type of items in the async list and in the returned list. - /// A task that will return a simple list - /// The list can't be null - [LinqTunnel] - public static Task> ToListAsync(this IAsyncEnumerable self) - { - if (self == null) - throw new ArgumentNullException(nameof(self)); - - static async Task> ToList(IAsyncEnumerable self) - { - List ret = new(); - await foreach (T i in self) - ret.Add(i); - return ret; - } - - return ToList(self); - } - /// /// If the enumerable is empty, execute an action. /// @@ -197,104 +76,5 @@ namespace Kyoo.Utils foreach (T i in self) action(i); } - - /// - /// A foreach used as a function with a little specificity: the list can be null. - /// - /// The list to enumerate. If this is null, the function result in a no-op - /// The action to execute for each arguments - public static void ForEach(this IEnumerable? self, Action action) - { - if (self == null) - return; - foreach (object i in self) - action(i); - } - - /// - /// A foreach used as a function with a little specificity: the list can be null. - /// - /// The list to enumerate. If this is null, the function result in a no-op - /// The action to execute for each arguments - /// A representing the asynchronous operation. - public static async Task ForEachAsync(this IEnumerable? self, Func action) - { - if (self == null) - return; - foreach (object i in self) - await action(i); - } - - /// - /// A foreach used as a function with a little specificity: the list can be null. - /// - /// The list to enumerate. If this is null, the function result in a no-op - /// The asynchronous action to execute for each arguments - /// The type of items in the list. - /// A representing the asynchronous operation. - public static async Task ForEachAsync(this IEnumerable? self, Func action) - { - if (self == null) - return; - foreach (T i in self) - await action(i); - } - - /// - /// A foreach used as a function with a little specificity: the list can be null. - /// - /// The async list to enumerate. If this is null, the function result in a no-op - /// The action to execute for each arguments - /// The type of items in the list. - /// A representing the asynchronous operation. - public static async Task ForEachAsync(this IAsyncEnumerable? self, Action action) - { - if (self == null) - return; - await foreach (T i in self) - action(i); - } - - /// - /// Split a list in a small chunk of data. - /// - /// The list to split - /// The number of items in each chunk - /// The type of data in the initial list. - /// A list of chunks - [LinqTunnel] - public static IEnumerable> BatchBy(this List list, int countPerList) - { - for (int i = 0; i < list.Count; i += countPerList) - yield return list.GetRange(i, Math.Min(list.Count - i, countPerList)); - } - - /// - /// Split a list in a small chunk of data. - /// - /// The list to split - /// The number of items in each chunk - /// The type of data in the initial list. - /// A list of chunks - [LinqTunnel] - public static IEnumerable BatchBy(this IEnumerable list, int countPerList) - { - T[] ret = new T[countPerList]; - int i = 0; - - using IEnumerator enumerator = list.GetEnumerator(); - while (enumerator.MoveNext()) - { - ret[i] = enumerator.Current; - i++; - if (i < countPerList) - continue; - i = 0; - yield return ret; - } - - Array.Resize(ref ret, i); - yield return ret; - } } } diff --git a/back/src/Kyoo.Abstractions/Utility/Merger.cs b/back/src/Kyoo.Abstractions/Utility/Merger.cs index 951f379b..267f18fb 100644 --- a/back/src/Kyoo.Abstractions/Utility/Merger.cs +++ b/back/src/Kyoo.Abstractions/Utility/Merger.cs @@ -17,13 +17,10 @@ // along with Kyoo. If not, see . using System; -using System.Collections; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Reflection; using JetBrains.Annotations; -using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Attributes; namespace Kyoo.Utils @@ -33,99 +30,9 @@ namespace Kyoo.Utils /// public static class Merger { - /// - /// Merge two lists, can keep duplicates or remove them. - /// - /// The first enumerable to merge - /// The second enumerable to merge, if items from this list are equals to one from the first, they are not kept - /// Equality function to compare items. If this is null, duplicated elements are kept - /// The type of items in the lists to merge. - /// The two list merged as an array - [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static T[] MergeLists(IEnumerable? first, - IEnumerable? second, - Func? isEqual = null) - { - if (first == null) - return second?.ToArray(); - if (second == null) - return first.ToArray(); - if (isEqual == null) - return first.Concat(second).ToArray(); - List list = first.ToList(); - return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToArray(); - } - - /// - /// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept. - /// - /// The first dictionary to merge - /// The second dictionary to merge - /// The type of the keys in dictionaries - /// The type of values in the dictionaries - /// The first dictionary with the missing elements of . - /// - [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static IDictionary MergeDictionaries(IDictionary? first, - IDictionary? second) - { - return MergeDictionaries(first, second, out bool _); - } - - /// - /// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept. - /// - /// The first dictionary to merge - /// The second dictionary to merge - /// - /// true if a new items has been added to the dictionary, false otherwise. - /// - /// The type of the keys in dictionaries - /// The type of values in the dictionaries - /// The first dictionary with the missing elements of . - [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static IDictionary MergeDictionaries(IDictionary? first, - IDictionary? second, - out bool hasChanged) - { - if (first == null) - { - hasChanged = true; - return second; - } - - hasChanged = false; - if (second == null) - return first; - foreach ((T key, T2 value) in second) - { - bool success = first.TryAdd(key, value); - hasChanged |= success; - - if (success || first[key]?.Equals(default) == false || value?.Equals(default) != false) - continue; - first[key] = value; - hasChanged = true; - } - - return first; - } - /// /// Merge two dictionary, if the same key is found on both dictionary, the values of the second one is kept. /// - /// - /// The only difference in this function compared to - /// - /// is the way is calculated and the order of the arguments. - /// - /// MergeDictionaries(first, second); - /// - /// will do the same thing as - /// - /// CompleteDictionaries(second, first, out bool _); - /// - /// /// The first dictionary to merge /// The second dictionary to merge /// @@ -138,7 +45,7 @@ namespace Kyoo.Utils /// set to those of . /// [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static IDictionary CompleteDictionaries(IDictionary? first, + public static IDictionary? CompleteDictionaries(IDictionary? first, IDictionary? second, out bool hasChanged) { @@ -160,14 +67,8 @@ namespace Kyoo.Utils /// /// Set every non-default values of seconds to the corresponding property of second. /// Dictionaries are handled like anonymous objects with a property per key/pair value - /// (see - /// - /// for more details). /// At the end, the OnMerge method of first will be called if first is a /// - /// - /// This does the opposite of . - /// /// /// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "foo"} /// @@ -182,19 +83,16 @@ namespace Kyoo.Utils /// /// Fields of T will be completed /// - /// If first is null - public static T Complete([NotNull] T first, + public static T Complete(T first, T? second, [InstantHandle] Func? where = null) { - if (first == null) - throw new ArgumentNullException(nameof(first)); if (second == null) return first; Type type = typeof(T); IEnumerable properties = type.GetProperties() - .Where(x => x.CanRead && x.CanWrite + .Where(x => x is { CanRead: true, CanWrite: true } && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); if (where != null) @@ -202,17 +100,16 @@ namespace Kyoo.Utils foreach (PropertyInfo property in properties) { - object value = property.GetValue(second); - object defaultValue = property.GetCustomAttribute()?.Value - ?? property.PropertyType.GetClrDefault(); + object? value = property.GetValue(second); - if (value?.Equals(defaultValue) != false || value.Equals(property.GetValue(first))) + if (value?.Equals(property.GetValue(first)) == true) continue; + if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) { Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) .GenericTypeArguments; - object[] parameters = + object?[] parameters = { property.GetValue(first), value, @@ -222,8 +119,8 @@ namespace Kyoo.Utils typeof(Merger), nameof(CompleteDictionaries), dictionaryTypes, - parameters); - if ((bool)parameters[2]) + parameters)!; + if ((bool)parameters[2]!) property.SetValue(first, newDictionary); } else @@ -234,109 +131,5 @@ namespace Kyoo.Utils merge.OnMerge(second); return first; } - - /// - /// This will set missing values of to the corresponding values of . - /// Enumerable will be merged (concatenated) and Dictionaries too. - /// At the end, the OnMerge method of first will be called if first is a . - /// - /// - /// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"} - /// - /// - /// The object to complete - /// - /// - /// Missing fields of first will be completed by fields of this item. If second is null, the function no-op. - /// - /// - /// Filter fields that will be merged - /// - /// Fields of T will be merged - /// - [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] - public static T Merge(T? first, - T? second, - [InstantHandle] Func? where = null) - { - if (first == null) - return second; - if (second == null) - return first; - - Type type = typeof(T); - IEnumerable properties = type.GetProperties() - .Where(x => x.CanRead && x.CanWrite - && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); - - if (where != null) - properties = properties.Where(where); - - foreach (PropertyInfo property in properties) - { - object oldValue = property.GetValue(first); - object newValue = property.GetValue(second); - object defaultValue = property.PropertyType.GetClrDefault(); - - if (oldValue?.Equals(defaultValue) != false) - property.SetValue(first, newValue); - else if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) - { - Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) - .GenericTypeArguments; - object[] parameters = - { - oldValue, - newValue, - false - }; - object newDictionary = Utility.RunGenericMethod( - typeof(Merger), - nameof(MergeDictionaries), - dictionaryTypes, - parameters); - if ((bool)parameters[2]) - property.SetValue(first, newDictionary); - } - else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) - && property.PropertyType != typeof(string)) - { - Type enumerableType = Utility.GetGenericDefinition(property.PropertyType, typeof(IEnumerable<>)) - .GenericTypeArguments - .First(); - Func equalityComparer = enumerableType.IsAssignableTo(typeof(IResource)) - ? (x, y) => x.Slug == y.Slug - : null; - property.SetValue(first, Utility.RunGenericMethod( - typeof(Merger), - nameof(MergeLists), - enumerableType, - oldValue, newValue, equalityComparer)); - } - } - - if (first is IOnMerge merge) - merge.OnMerge(second); - return first; - } - - /// - /// Set every fields of to the default value. - /// - /// The object to nullify - /// Fields of T will be nullified - /// - public static T Nullify(T obj) - { - Type type = typeof(T); - foreach (PropertyInfo property in type.GetProperties()) - { - if (!property.CanWrite || property.GetCustomAttribute() != null) - continue; - property.SetValue(obj, property.PropertyType.GetClrDefault()); - } - - return obj; - } } } diff --git a/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs b/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs index 42c01027..973ec3ea 100644 --- a/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs +++ b/back/src/Kyoo.Abstractions/Utility/TaskUtils.cs @@ -18,7 +18,6 @@ using System; using System.Threading.Tasks; -using JetBrains.Annotations; namespace Kyoo.Utils { @@ -49,37 +48,5 @@ namespace Kyoo.Utils return x.Result; }, TaskContinuationOptions.ExecuteSynchronously); } - - /// - /// Map the result of a task to another result. - /// - /// The task to map. - /// The mapper method, it take the task's result as a parameter and should return the new result. - /// The type of returns of the given task - /// The resulting task after the mapping method - /// A task wrapping the initial task and mapping the initial result. - /// The source task has been canceled. - public static Task Map(this Task task, Func map) - { - return task.ContinueWith(x => - { - if (x.IsFaulted) - x.Exception!.InnerException!.ReThrow(); - if (x.IsCanceled) - throw new TaskCanceledException(); - return map(x.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - } - - /// - /// A method to return the a default value from a task if the initial task is null. - /// - /// The initial task - /// The type that the task will return - /// A non-null task. - public static Task DefaultIfNull(Task? value) - { - return value ?? Task.FromResult(default); - } } } diff --git a/back/src/Kyoo.Abstractions/Utility/Utility.cs b/back/src/Kyoo.Abstractions/Utility/Utility.cs index 57e261df..ca9c513e 100644 --- a/back/src/Kyoo.Abstractions/Utility/Utility.cs +++ b/back/src/Kyoo.Abstractions/Utility/Utility.cs @@ -41,8 +41,6 @@ namespace Kyoo.Utils /// True if the expression is a member, false otherwise public static bool IsPropertyExpression(LambdaExpression ex) { - if (ex == null) - return false; return ex.Body is MemberExpression || (ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression); } @@ -57,7 +55,7 @@ namespace Kyoo.Utils { if (!IsPropertyExpression(ex)) throw new ArgumentException($"{ex} is not a property expression."); - MemberExpression member = ex.Body.NodeType == ExpressionType.Convert + MemberExpression? member = ex.Body.NodeType == ExpressionType.Convert ? ((UnaryExpression)ex.Body).Operand as MemberExpression : ex.Body as MemberExpression; return member!.Member.Name; @@ -92,18 +90,6 @@ namespace Kyoo.Utils return str; } - /// - /// Get the default value of a type. - /// - /// The type to get the default value - /// The default value of the given type. - public static object GetClrDefault(this Type type) - { - return type.IsValueType - ? Activator.CreateInstance(type) - : null; - } - /// /// Return every in the inheritance tree of the parameter (interfaces are not returned) /// @@ -194,13 +180,6 @@ namespace Kyoo.Utils Type[] generics, object[] args) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - if (generics == null) - throw new ArgumentNullException(nameof(generics)); - if (args == null) - throw new ArgumentNullException(nameof(args)); - MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public) .Where(x => x.Name == name) .Where(x => x.GetGenericArguments().Length == generics.Length) @@ -295,12 +274,11 @@ namespace Kyoo.Utils /// The return of the method you wanted to run. /// /// - [PublicAPI] - public static T RunGenericMethod( + public static T? RunGenericMethod( Type owner, string methodName, Type[] types, - params object[] args) + params object?[] args) { if (owner == null) throw new ArgumentNullException(nameof(owner)); @@ -311,7 +289,7 @@ namespace Kyoo.Utils if (types.Length < 1) throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed."); MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args); - return (T)method.MakeGenericMethod(types).Invoke(null, args); + return (T?)method.MakeGenericMethod(types).Invoke(null, args); } /// diff --git a/back/src/Kyoo.Authentication/Views/AuthApi.cs b/back/src/Kyoo.Authentication/Views/AuthApi.cs index a3b8135c..2040ac13 100644 --- a/back/src/Kyoo.Authentication/Views/AuthApi.cs +++ b/back/src/Kyoo.Authentication/Views/AuthApi.cs @@ -27,6 +27,7 @@ using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; using Kyoo.Authentication.Models; using Kyoo.Authentication.Models.DTO; +using Kyoo.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; @@ -229,7 +230,7 @@ namespace Kyoo.Authentication.Views try { user.Id = userID; - return await _users.Edit(user, true); + return await _users.Edit(user); } catch (ItemNotFoundException) { @@ -252,14 +253,15 @@ namespace Kyoo.Authentication.Views [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))] - public async Task> PatchMe(User user) + public async Task> PatchMe(PartialResource user) { if (!int.TryParse(User.FindFirstValue(Claims.Id), out int userID)) return Unauthorized(new RequestError("User not authenticated or token invalid.")); try { - user.Id = userID; - return await _users.Edit(user, false); + if (user.Id.HasValue && user.Id != userID) + throw new ArgumentException("Can't edit your user id."); + return await _users.Patch(userID, TryUpdateModelAsync); } catch (ItemNotFoundException) { diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs index b134e76b..0cab6155 100644 --- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -284,12 +284,12 @@ namespace Kyoo.Core.Controllers (Show s, nameof(Show.Seasons)) => _SetRelation(s, SeasonRepository.GetAll(x => x.Show.Id == obj.Id), (x, y) => x.Seasons = y, - (x, y) => { x.Show = y; x.ShowID = y.Id; }), + (x, y) => { x.Show = y; x.ShowId = y.Id; }), (Show s, nameof(Show.Episodes)) => _SetRelation(s, EpisodeRepository.GetAll(x => x.Show.Id == obj.Id), (x, y) => x.Episodes = y, - (x, y) => { x.Show = y; x.ShowID = y.Id; }), + (x, y) => { x.Show = y; x.ShowId = y.Id; }), (Show s, nameof(Show.Collections)) => CollectionRepository .GetAll(x => x.Shows.Any(y => y.Id == obj.Id)) @@ -300,21 +300,21 @@ namespace Kyoo.Core.Controllers .Then(x => { s.Studio = x; - s.StudioID = x?.Id ?? 0; + s.StudioId = x?.Id ?? 0; }), (Season s, nameof(Season.Episodes)) => _SetRelation(s, EpisodeRepository.GetAll(x => x.Season.Id == obj.Id), (x, y) => x.Episodes = y, - (x, y) => { x.Season = y; x.SeasonID = y.Id; }), + (x, y) => { x.Season = y; x.SeasonId = y.Id; }), (Season s, nameof(Season.Show)) => ShowRepository .GetOrDefault(x => x.Seasons.Any(y => y.Id == obj.Id)) .Then(x => { s.Show = x; - s.ShowID = x?.Id ?? 0; + s.ShowId = x?.Id ?? 0; }), @@ -323,7 +323,7 @@ namespace Kyoo.Core.Controllers .Then(x => { e.Show = x; - e.ShowID = x?.Id ?? 0; + e.ShowId = x?.Id ?? 0; }), (Episode e, nameof(Episode.Season)) => SeasonRepository @@ -331,18 +331,18 @@ namespace Kyoo.Core.Controllers .Then(x => { e.Season = x; - e.SeasonID = x?.Id ?? 0; + e.SeasonId = x?.Id ?? 0; }), (Episode e, nameof(Episode.PreviousEpisode)) => EpisodeRepository .GetAll( - where: x => x.ShowID == e.ShowID, + where: x => x.ShowId == e.ShowId, limit: new Pagination(1, e.Id, true) ).Then(x => e.PreviousEpisode = x.FirstOrDefault()), (Episode e, nameof(Episode.NextEpisode)) => EpisodeRepository .GetAll( - where: x => x.ShowID == e.ShowID, + where: x => x.ShowId == e.ShowId, limit: new Pagination(1, e.Id) ).Then(x => e.NextEpisode = x.FirstOrDefault()), @@ -438,10 +438,17 @@ namespace Kyoo.Core.Controllers } /// - public Task Edit(T item, bool resetOld) + public Task Edit(T item) where T : class, IResource { - return GetRepository().Edit(item, resetOld); + return GetRepository().Edit(item); + } + + /// + public Task Patch(int id, Func> patch) + where T : class, IResource + { + return GetRepository().Patch(id, patch); } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index fe4e7412..f1a49d41 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -64,7 +64,7 @@ namespace Kyoo.Core.Controllers // Edit episode slugs when the show's slug changes. shows.OnEdited += (show) => { - List episodes = _database.Episodes.AsTracking().Where(x => x.ShowID == show.Id).ToList(); + List episodes = _database.Episodes.AsTracking().Where(x => x.ShowId == show.Id).ToList(); foreach (Episode ep in episodes) { ep.ShowSlug = show.Slug; @@ -77,7 +77,7 @@ namespace Kyoo.Core.Controllers /// public Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID && x.SeasonNumber == seasonNumber && x.EpisodeNumber == episodeNumber); } @@ -111,7 +111,7 @@ namespace Kyoo.Core.Controllers /// public Task GetAbsolute(int showID, int absoluteNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID && x.AbsoluteNumber == absoluteNumber); } @@ -142,12 +142,12 @@ namespace Kyoo.Core.Controllers public override async Task Create(Episode obj) { await base.Create(obj); - obj.ShowSlug = obj.Show?.Slug ?? _database.Shows.First(x => x.Id == obj.ShowID).Slug; + obj.ShowSlug = obj.Show?.Slug ?? _database.Shows.First(x => x.Id == obj.ShowId).Slug; _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync(() => obj.SeasonNumber != null && obj.EpisodeNumber != null - ? Get(obj.ShowID, obj.SeasonNumber.Value, obj.EpisodeNumber.Value) - : GetAbsolute(obj.ShowID, obj.AbsoluteNumber.Value)); + ? Get(obj.ShowId, obj.SeasonNumber.Value, obj.EpisodeNumber.Value) + : GetAbsolute(obj.ShowId, obj.AbsoluteNumber.Value)); OnResourceCreated(obj); return obj; } @@ -156,14 +156,14 @@ namespace Kyoo.Core.Controllers protected override async Task Validate(Episode resource) { await base.Validate(resource); - if (resource.ShowID <= 0) + if (resource.ShowId <= 0) { if (resource.Show == null) { throw new ArgumentException($"Can't store an episode not related " + - $"to any show (showID: {resource.ShowID})."); + $"to any show (showID: {resource.ShowId})."); } - resource.ShowID = resource.Show.Id; + resource.ShowId = resource.Show.Id; } } @@ -173,12 +173,12 @@ namespace Kyoo.Core.Controllers if (obj == null) throw new ArgumentNullException(nameof(obj)); - int epCount = await _database.Episodes.Where(x => x.ShowID == obj.ShowID).Take(2).CountAsync(); + int epCount = await _database.Episodes.Where(x => x.ShowId == obj.ShowId).Take(2).CountAsync(); _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); await base.Delete(obj); if (epCount == 1) - await _shows.Delete(obj.ShowID); + await _shows.Delete(obj.ShowId); } } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index 33dd9b8b..0a6350f8 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -100,7 +100,11 @@ namespace Kyoo.Core.Controllers => throw new InvalidOperationException(); /// - public override Task Edit(ILibraryItem obj, bool resetOld) + public override Task Edit(ILibraryItem obj) + => throw new InvalidOperationException(); + + /// + public override Task Patch(int id, Func> patch) => throw new InvalidOperationException(); /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 8e421c7c..0ced12d0 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -144,7 +144,7 @@ namespace Kyoo.Core.Controllers /// The reference item (the AfterID query) /// True if the following page should be returned, false for the previous. /// An expression ready to be added to a Where close of a sorted query to handle the AfterID - protected Expression> KeysetPaginatate( + protected Expression> KeysetPaginate( Sort sort, T reference, bool next = true) @@ -155,22 +155,22 @@ namespace Kyoo.Core.Controllers ParameterExpression x = Expression.Parameter(typeof(T), "x"); ConstantExpression referenceC = Expression.Constant(reference, typeof(T)); - IEnumerable.By> _GetSortsBy(Sort sort) + IEnumerable.By> GetSortsBy(Sort sort) { return sort switch { - Sort.Default => _GetSortsBy(DefaultSort), + Sort.Default => GetSortsBy(DefaultSort), Sort.By @sortBy => new[] { sortBy }, - Sort.Conglomerate(var list) => list.SelectMany(_GetSortsBy), + Sort.Conglomerate(var list) => list.SelectMany(GetSortsBy), _ => Array.Empty.By>(), }; } - // Don't forget that every sorts must end with a ID sort (to differenciate equalities). + // Don't forget that every sorts must end with a ID sort (to differentiate equalities). Sort.By id = new(x => x.Id); - IEnumerable.By> sorts = _GetSortsBy(sort).Append(id); + IEnumerable.By> sorts = GetSortsBy(sort).Append(id); - BinaryExpression filter = null; + BinaryExpression? filter = null; List.By> previousSteps = new(); // TODO: Add an outer query >= for perf // PERF: See https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic @@ -180,9 +180,9 @@ namespace Kyoo.Core.Controllers PropertyInfo property = typeof(T).GetProperty(key); // Comparing a value with null always return false so we short opt < > comparisons with null. - if (property.GetValue(reference) == null) + if (property!.GetValue(reference) == null) { - previousSteps.Add(new(key, desc)); + previousSteps.Add(new Sort.By(key, desc)); continue; } @@ -206,7 +206,7 @@ namespace Kyoo.Core.Controllers // Comparing a value with null always return false for nulls so we must add nulls to the results manually. // Postgres sorts them after values so we will do the same - // We only add this condition if the collumn type is nullable + // We only add this condition if the column type is nullable if (Nullable.GetUnderlyingType(property.PropertyType) != null) { BinaryExpression equalNull = Expression.Equal(xkey, Expression.Constant(null)); @@ -223,7 +223,7 @@ namespace Kyoo.Core.Controllers previousSteps.Add(new(key, desc)); } - return Expression.Lambda>(filter, x); + return Expression.Lambda>(filter!, x); } /// @@ -316,7 +316,7 @@ namespace Kyoo.Core.Controllers if (limit?.AfterID != null) { T reference = await Get(limit.AfterID.Value); - query = query.Where(KeysetPaginatate(sort, reference, !limit.Reverse)); + query = query.Where(KeysetPaginate(sort, reference, !limit.Reverse)); } if (limit?.Reverse == true) query = query.Reverse(); @@ -343,12 +343,9 @@ namespace Kyoo.Core.Controllers await Validate(obj); if (obj is IThumbnails thumbs) { - if (thumbs.Poster != null) - Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Added; - if (thumbs.Thumbnail != null) - Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Added; - if (thumbs.Logo != null) - Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Added; + Database.Entry(thumbs).Reference(x => x.Poster).IsModified = thumbs.Poster != null; + Database.Entry(thumbs).Reference(x => x.Thumbnail).IsModified = thumbs.Thumbnail != null; + Database.Entry(thumbs).Reference(x => x.Logo).IsModified = thumbs.Logo != null; } return obj; } @@ -376,9 +373,6 @@ namespace Kyoo.Core.Controllers { try { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - T old = await GetOrDefault(obj.Slug); if (old != null) return old; @@ -392,21 +386,16 @@ namespace Kyoo.Core.Controllers } /// - public virtual async Task Edit(T edited, bool resetOld) + public virtual async Task Edit(T edited) { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled; Database.ChangeTracker.LazyLoadingEnabled = false; try { T old = await GetWithTracking(edited.Id); - if (resetOld) - old = Merger.Nullify(old); Merger.Complete(old, edited, x => x.GetCustomAttribute() == null); - await EditRelations(old, edited, resetOld); + await EditRelations(old, edited, true); await Database.SaveChangesAsync(); OnEdited?.Invoke(old); return old; @@ -418,6 +407,28 @@ namespace Kyoo.Core.Controllers } } + /// + public virtual async Task Patch(int id, Func> patch) + { + bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled; + Database.ChangeTracker.LazyLoadingEnabled = false; + try + { + T resource = await GetWithTracking(id); + + if (!await patch(resource)) + throw new ArgumentException("Could not patch resource"); + await Database.SaveChangesAsync(); + OnEdited?.Invoke(resource); + return resource; + } + finally + { + Database.ChangeTracker.LazyLoadingEnabled = lazyLoading; + Database.ChangeTracker.Clear(); + } + } + /// /// An overridable method to edit relation of a resource. /// @@ -434,12 +445,11 @@ namespace Kyoo.Core.Controllers /// A representing the asynchronous operation. protected virtual Task EditRelations(T resource, T changed, bool resetOld) { - if (resource is IThumbnails thumbs) + if (resource is IThumbnails thumbs && changed is IThumbnails chng) { - // FIXME: I think this throws if poster is set to null. - Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Modified; - Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Modified; - Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Modified; + Database.Entry(thumbs).Reference(x => x.Poster).IsModified = thumbs.Poster != chng.Poster; + Database.Entry(thumbs).Reference(x => x.Thumbnail).IsModified = thumbs.Thumbnail != chng.Thumbnail; + Database.Entry(thumbs).Reference(x => x.Logo).IsModified = thumbs.Logo != chng.Logo; } return Validate(resource); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 37a2e820..0265fc4a 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -55,7 +55,7 @@ namespace Kyoo.Core.Controllers // Edit seasons slugs when the show's slug changes. shows.OnEdited += (show) => { - List seasons = _database.Seasons.AsTracking().Where(x => x.ShowID == show.Id).ToList(); + List seasons = _database.Seasons.AsTracking().Where(x => x.ShowId == show.Id).ToList(); foreach (Season season in seasons) { season.ShowSlug = show.Slug; @@ -86,7 +86,7 @@ namespace Kyoo.Core.Controllers /// public Task GetOrDefault(int showID, int seasonNumber) { - return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID + return _database.Seasons.FirstOrDefaultAsync(x => x.ShowId == showID && x.SeasonNumber == seasonNumber); } @@ -112,9 +112,9 @@ namespace Kyoo.Core.Controllers public override async Task Create(Season obj) { await base.Create(obj); - obj.ShowSlug = _database.Shows.First(x => x.Id == obj.ShowID).Slug; + obj.ShowSlug = _database.Shows.First(x => x.Id == obj.ShowId).Slug; _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync(() => Get(obj.ShowID, obj.SeasonNumber)); + await _database.SaveChangesAsync(() => Get(obj.ShowId, obj.SeasonNumber)); OnResourceCreated(obj); return obj; } @@ -123,14 +123,14 @@ namespace Kyoo.Core.Controllers protected override async Task Validate(Season resource) { await base.Validate(resource); - if (resource.ShowID <= 0) + if (resource.ShowId <= 0) { if (resource.Show == null) { throw new ArgumentException($"Can't store a season not related to any show " + - $"(showID: {resource.ShowID})."); + $"(showID: {resource.ShowId})."); } - resource.ShowID = resource.Show.Id; + resource.ShowId = resource.Show.Id; } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index 2bfb8fda..810300a3 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -97,7 +97,7 @@ namespace Kyoo.Core.Controllers if (resource.Studio != null) { resource.Studio = await _studios.CreateIfNotExists(resource.Studio); - resource.StudioID = resource.Studio.Id; + resource.StudioId = resource.Studio.Id; } if (resource.People != null) diff --git a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs index 2a786bd8..a593ac9a 100644 --- a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs +++ b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs @@ -16,12 +16,14 @@ // 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.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; +using Kyoo.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -162,11 +164,11 @@ namespace Kyoo.Core.Api public async Task> Edit([FromBody] T resource) { if (resource.Id > 0) - return await Repository.Edit(resource, true); + return await Repository.Edit(resource); T old = await Repository.Get(resource.Slug); resource.Id = old.Id; - return await Repository.Edit(resource, true); + return await Repository.Edit(resource); } /// @@ -185,14 +187,15 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> Patch([FromBody] T resource) + public async Task> Patch([FromBody] PartialResource resource) { - if (resource.Id > 0) - return await Repository.Edit(resource, false); + if (resource.Id.HasValue) + return await Repository.Patch(resource.Id.Value, TryUpdateModelAsync); + if (resource.Slug == null) + throw new ArgumentException("Either the Id or the slug of the resource has to be defined to edit it."); T old = await Repository.Get(resource.Slug); - resource.Id = old.Id; - return await Repository.Edit(resource, false); + return await Repository.Patch(old.Id, TryUpdateModelAsync); } /// diff --git a/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs b/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs index 873c398f..90c72539 100644 --- a/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs +++ b/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs @@ -83,7 +83,7 @@ namespace Kyoo.Core.Api [FromQuery] Pagination pagination) { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.StudioID, x => x.Studio.Slug)), + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.StudioId, x => x.Studio.Slug)), Sort.From(sortBy), pagination ); diff --git a/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs b/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs index d336e22c..f26bbe30 100644 --- a/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs @@ -84,7 +84,7 @@ namespace Kyoo.Core.Api [FromQuery] Pagination pagination) { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.SeasonID, x => x.Season.Slug)), + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.SeasonId, x => x.Season.Slug)), Sort.From(sortBy), pagination ); diff --git a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs index 68d44eb1..2084291e 100644 --- a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs @@ -86,7 +86,7 @@ namespace Kyoo.Core.Api [FromQuery] Pagination pagination) { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowId, x => x.Show.Slug)), Sort.From(sortBy), pagination ); @@ -121,7 +121,7 @@ namespace Kyoo.Core.Api [FromQuery] Pagination pagination) { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowId, x => x.Show.Slug)), Sort.From(sortBy), pagination ); diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index 3d794f2c..aa9e5ca5 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -306,13 +306,13 @@ namespace Kyoo.Postgresql .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() - .HasIndex(x => new { x.ShowID, x.SeasonNumber }) + .HasIndex(x => new { ShowID = x.ShowId, x.SeasonNumber }) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() - .HasIndex(x => new { x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber }) + .HasIndex(x => new { ShowID = x.ShowId, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber }) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) diff --git a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs index f6aa7de4..383d8fbe 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs @@ -140,16 +140,10 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get())); } - [Fact] - public async Task EditNullTest() - { - await Assert.ThrowsAsync(() => _repository.Edit(null!, false)); - } - [Fact] public async Task EditNonExistingTest() { - await Assert.ThrowsAsync(() => _repository.Edit(new T { Id = 56 }, false)); + await Assert.ThrowsAsync(() => _repository.Edit(new T { Id = 56 })); } [Fact] @@ -170,12 +164,6 @@ namespace Kyoo.Tests.Database await Assert.ThrowsAsync(() => _repository.Get(x => x.Slug == "non-existing")); } - [Fact] - public async Task GetExpressionNullTest() - { - await Assert.ThrowsAsync(() => _repository.Get((Expression>)null!)); - } - [Fact] public async Task GetOrDefaultTest() { diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs index ef38efb1..2094afdb 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -66,29 +66,19 @@ namespace Kyoo.Tests.Database Assert.Equal("2!", ret.Slug); } - [Fact] - public async Task CreateWithoutNameTest() - { - Collection collection = TestSample.GetNew(); - collection.Name = null; - await Assert.ThrowsAsync(() => _repository.Create(collection)); - } - [Fact] public async Task CreateWithExternalIdTest() { Collection collection = TestSample.GetNew(); - collection.ExternalId = new[] + collection.ExternalId = new Dictionary { - new MetadataId + ["1"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, - new MetadataId + ["2"] = new() { - Provider = TestSample.GetNew(), Link = "new-provider-link", DataId = "new-id" } @@ -96,7 +86,6 @@ namespace Kyoo.Tests.Database await _repository.Create(collection); Collection retrieved = await _repository.Get(2); - 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()); @@ -107,11 +96,8 @@ namespace Kyoo.Tests.Database { Collection value = await _repository.Get(TestSample.Get().Slug); value.Name = "New Title"; - value.Images = new Dictionary - { - [Images.Poster] = "new-poster" - }; - await _repository.Edit(value, false); + value.Poster = new Image("new-poster"); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections.FirstAsync(); @@ -123,21 +109,19 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { Collection value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["test"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); @@ -147,40 +131,36 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Collection value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new Dictionary { - new() + ["toto"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataId + value.ExternalId.Add("test", new MetadataId { - Provider = TestSample.GetNew(), Link = "link", DataId = "id" }); - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); Collection retrieved = await database.Collections .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .FirstAsync(); KAssert.DeepEqual(value, retrieved); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index 8ee85408..dfb91af4 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -57,10 +57,10 @@ namespace Kyoo.Tests.Database Assert.Equal($"{TestSample.Get().Slug}-s1e1", episode.Slug); Show show = new() { - Id = episode.ShowID, + Id = episode.ShowId, Slug = "new-slug" }; - await Repositories.LibraryManager.ShowRepository.Edit(show, false); + await Repositories.LibraryManager.ShowRepository.Edit(show); episode = await _repository.Get(1); Assert.Equal("new-slug-s1e1", episode.Slug); } @@ -74,8 +74,8 @@ namespace Kyoo.Tests.Database { Id = 1, SeasonNumber = 2, - ShowID = 1 - }, false); + ShowId = 1 + }); Assert.Equal($"{TestSample.Get().Slug}-s2e1", episode.Slug); episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-s2e1", episode.Slug); @@ -90,8 +90,8 @@ namespace Kyoo.Tests.Database { Id = 1, EpisodeNumber = 2, - ShowID = 1 - }, false); + ShowId = 1 + }); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); @@ -102,7 +102,7 @@ namespace Kyoo.Tests.Database { Episode episode = await _repository.Create(new Episode { - ShowID = TestSample.Get().Id, + ShowId = TestSample.Get().Id, SeasonNumber = 2, EpisodeNumber = 4 }); @@ -129,10 +129,10 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode()); Show show = new() { - Id = episode.ShowID, + Id = episode.ShowId, Slug = "new-slug" }; - await Repositories.LibraryManager.ShowRepository.Edit(show, false); + await Repositories.LibraryManager.ShowRepository.Edit(show); episode = await _repository.Get(2); Assert.Equal($"new-slug-3", episode.Slug); } @@ -145,8 +145,8 @@ namespace Kyoo.Tests.Database { Id = 2, AbsoluteNumber = 56, - ShowID = 1 - }, false); + ShowId = 1 + }); Assert.Equal($"{TestSample.Get().Slug}-56", episode.Slug); episode = await _repository.Get(2); Assert.Equal($"{TestSample.Get().Slug}-56", episode.Slug); @@ -161,8 +161,8 @@ namespace Kyoo.Tests.Database Id = 2, SeasonNumber = 1, EpisodeNumber = 2, - ShowID = 1 - }, false); + ShowId = 1 + }); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(2); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); @@ -174,49 +174,25 @@ namespace Kyoo.Tests.Database Episode episode = await _repository.Get(1); episode.SeasonNumber = null; episode.AbsoluteNumber = 12; - episode = await _repository.Edit(episode, true); + episode = await _repository.Edit(episode); Assert.Equal($"{TestSample.Get().Slug}-12", episode.Slug); episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-12", episode.Slug); } - [Fact] - public async Task MovieEpisodeTest() - { - Episode episode = await _repository.Create(TestSample.GetMovieEpisode()); - Assert.Equal(TestSample.Get().Slug, episode.Slug); - episode = await _repository.Get(3); - Assert.Equal(TestSample.Get().Slug, episode.Slug); - } - - [Fact] - public async Task MovieEpisodeEditTest() - { - await _repository.Create(TestSample.GetMovieEpisode()); - await Repositories.LibraryManager.Edit(new Show - { - Id = 1, - Slug = "john-wick" - }, false); - Episode episode = await _repository.Get(3); - Assert.Equal("john-wick", episode.Slug); - } - [Fact] public async Task CreateWithExternalIdTest() { Episode value = TestSample.GetNew(); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["2"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, - new MetadataId + ["3"] = new() { - Provider = TestSample.GetNew(), Link = "new-provider-link", DataId = "new-id" } @@ -224,7 +200,6 @@ namespace Kyoo.Tests.Database await _repository.Create(value); Episode retrieved = await _repository.Get(2); - 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()); @@ -235,11 +210,8 @@ namespace Kyoo.Tests.Database { Episode value = await _repository.Get(TestSample.Get().Slug); value.Name = "New Title"; - value.Images = new Dictionary - { - [Images.Poster] = "new-poster" - }; - await _repository.Edit(value, false); + value.Poster = new Image("poster"); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); Episode retrieved = await database.Episodes.FirstAsync(); @@ -251,22 +223,18 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { Episode value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["1"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); - Episode retrieved = await database.Episodes - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Episode retrieved = await database.Episodes.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -275,41 +243,33 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Episode value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new Dictionary { - new() + ["toto"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - Episode retrieved = await database.Episodes - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Episode retrieved = await database.Episodes.FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataId + value.ExternalId.Add("test", new MetadataId { - Provider = TestSample.GetNew(), Link = "link", DataId = "id" }); - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - Episode retrieved = await database.Episodes - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Episode retrieved = await database.Episodes.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -326,7 +286,7 @@ namespace Kyoo.Tests.Database Episode value = new() { Name = "This is a test super title", - ShowID = 1, + ShowId = 1, AbsoluteNumber = 2 }; await _repository.Create(value); @@ -343,8 +303,8 @@ namespace Kyoo.Tests.Database Episode expected = TestSample.Get(); expected.Id = 0; - expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; - expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; + expected.ShowId = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; + expected.SeasonId = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; await _repository.Create(expected); KAssert.DeepEqual(expected, await _repository.Get(expected.Slug)); } @@ -355,8 +315,8 @@ namespace Kyoo.Tests.Database Episode expected = TestSample.Get(); KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get())); await _repository.Delete(TestSample.Get()); - expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; - expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; + expected.ShowId = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get())).Id; + expected.SeasonId = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get())).Id; KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(expected)); } } diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.cs deleted file mode 100644 index db572ea1..00000000 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryItemTest.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.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class LibraryItemTest : ALibraryItemTest - { - public LibraryItemTest(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class ALibraryItemTest - { - private readonly ILibraryItemRepository _repository; - private readonly RepositoryActivator _repositories; - - protected ALibraryItemTest(RepositoryActivator repositories) - { - _repositories = repositories; - _repository = repositories.LibraryManager.LibraryItemRepository; - } - - [Fact] - public async Task CountTest() - { - Assert.Equal(2, await _repository.GetCount()); - } - - [Fact] - public async Task GetShowTests() - { - LibraryItem expected = new(TestSample.Get()); - LibraryItem actual = await _repository.Get(1); - KAssert.DeepEqual(expected, actual); - } - - [Fact] - public async Task GetCollectionTests() - { - LibraryItem expected = new(TestSample.Get()); - LibraryItem actual = await _repository.Get(-1); - KAssert.DeepEqual(expected, actual); - } - - [Fact] - public async Task GetShowSlugTests() - { - LibraryItem expected = new(TestSample.Get()); - LibraryItem actual = await _repository.Get(TestSample.Get().Slug); - KAssert.DeepEqual(expected, actual); - } - - [Fact] - public async Task GetCollectionSlugTests() - { - LibraryItem expected = new(TestSample.Get()); - LibraryItem actual = await _repository.Get(TestSample.Get().Slug); - KAssert.DeepEqual(expected, actual); - } - - [Fact] - public async Task GetDuplicatedSlugTests() - { - await _repositories.LibraryManager.Create(new Collection - { - Slug = TestSample.Get().Slug, - Name = "name" - }); - await Assert.ThrowsAsync(() => _repository.Get(TestSample.Get().Slug)); - } - } -} diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs deleted file mode 100644 index 57a7deea..00000000 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs +++ /dev/null @@ -1,169 +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.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Kyoo.Postgresql; -using Kyoo.Utils; -using Microsoft.EntityFrameworkCore; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class LibraryTests : ALibraryTests - { - public LibraryTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class ALibraryTests : RepositoryTests - { - private readonly ILibraryRepository _repository; - - protected ALibraryTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.LibraryRepository; - } - - [Fact] - public async Task CreateWithoutPathTest() - { - Library library = TestSample.GetNew(); - library.Paths = null; - await Assert.ThrowsAsync(() => _repository.Create(library)); - } - - [Fact] - public async Task CreateWithEmptySlugTest() - { - Library library = TestSample.GetNew(); - library.Slug = string.Empty; - await Assert.ThrowsAsync(() => _repository.Create(library)); - } - - [Fact] - public async Task CreateWithNumberSlugTest() - { - Library library = TestSample.GetNew(); - library.Slug = "2"; - Library ret = await _repository.Create(library); - Assert.Equal("2!", ret.Slug); - } - - [Fact] - public async Task CreateWithoutNameTest() - { - Library library = TestSample.GetNew(); - library.Name = null; - await Assert.ThrowsAsync(() => _repository.Create(library)); - } - - [Fact] - public async Task CreateWithProvider() - { - Library library = TestSample.GetNew(); - library.Providers = new[] { TestSample.Get() }; - await _repository.Create(library); - Library retrieved = await _repository.Get(2); - await Repositories.LibraryManager.Load(retrieved, x => x.Providers); - Assert.Single(retrieved.Providers); - Assert.Equal(TestSample.Get().Slug, retrieved.Providers.First().Slug); - } - - [Fact] - public async Task EditTest() - { - Library value = await _repository.Get(TestSample.Get().Slug); - value.Paths = new[] { "/super", "/test" }; - value.Name = "New Title"; - Library edited = await _repository.Edit(value, false); - - await using DatabaseContext database = Repositories.Context.New(); - Library show = await database.Libraries.FirstAsync(); - - KAssert.DeepEqual(show, edited); - } - - [Fact] - public async Task EditProvidersTest() - { - Library value = await _repository.Get(TestSample.Get().Slug); - value.Providers = new[] - { - TestSample.GetNew() - }; - Library edited = await _repository.Edit(value, false); - - await using DatabaseContext database = Repositories.Context.New(); - Library show = await database.Libraries - .Include(x => x.Providers) - .FirstAsync(); - - show.Providers.ForEach(x => x.Libraries = null); - edited.Providers.ForEach(x => x.Libraries = null); - KAssert.DeepEqual(show, edited); - } - - [Fact] - public async Task AddProvidersTest() - { - Library value = await _repository.Get(TestSample.Get().Slug); - await Repositories.LibraryManager.Load(value, x => x.Providers); - value.Providers.Add(TestSample.GetNew()); - Library edited = await _repository.Edit(value, false); - - await using DatabaseContext database = Repositories.Context.New(); - Library show = await database.Libraries - .Include(x => x.Providers) - .FirstAsync(); - - show.Providers.ForEach(x => x.Libraries = null); - edited.Providers.ForEach(x => x.Libraries = null); - KAssert.DeepEqual(show, edited); - } - - [Theory] - [InlineData("test")] - [InlineData("super")] - [InlineData("title")] - [InlineData("TiTlE")] - [InlineData("SuPeR")] - public async Task SearchTest(string query) - { - Library value = new() - { - Slug = "super-test", - Name = "This is a test title", - Paths = new[] { "path" } - }; - await _repository.Create(value); - ICollection ret = await _repository.Search(query); - KAssert.DeepEqual(value, ret.First()); - } - } -} diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs index e6b58242..8bf3b9da 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs @@ -52,17 +52,15 @@ namespace Kyoo.Tests.Database public async Task CreateWithExternalIdTest() { People value = TestSample.GetNew(); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["2"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, - new MetadataId + ["1"] = new() { - Provider = TestSample.GetNew(), Link = "new-provider-link", DataId = "new-id" } @@ -70,7 +68,6 @@ namespace Kyoo.Tests.Database await _repository.Create(value); People retrieved = await _repository.Get(2); - 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()); @@ -81,11 +78,8 @@ namespace Kyoo.Tests.Database { People value = await _repository.Get(TestSample.Get().Slug); value.Name = "New Name"; - value.Images = new Dictionary - { - [Images.Poster] = "new-poster" - }; - await _repository.Edit(value, false); + value.Poster = new Image("poster"); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); People retrieved = await database.People.FirstAsync(); @@ -97,22 +91,18 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { People value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["toto"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); - People retrieved = await database.People - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + People retrieved = await database.People.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -121,41 +111,32 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { People value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new Dictionary { - new() + ["1"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - People retrieved = await database.People - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); - + People retrieved = await database.People.FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataId + value.ExternalId.Add("toto", new MetadataId { - Provider = TestSample.GetNew(), Link = "link", DataId = "id" }); - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - People retrieved = await database.People - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + People retrieved = await database.People.FirstAsync(); KAssert.DeepEqual(value, retrieved); } diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs deleted file mode 100644 index 49340367..00000000 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/ProviderTests.cs +++ /dev/null @@ -1,48 +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.Diagnostics.CodeAnalysis; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Xunit; -using Xunit.Abstractions; - -namespace Kyoo.Tests.Database -{ - namespace PostgreSQL - { - [Collection(nameof(Postgresql))] - public class ProviderTests : AProviderTests - { - public ProviderTests(PostgresFixture postgres, ITestOutputHelper output) - : base(new RepositoryActivator(output, postgres)) { } - } - } - - public abstract class AProviderTests : RepositoryTests - { - [SuppressMessage("ReSharper", "NotAccessedField.Local")] - private readonly IProviderRepository _repository; - - protected AProviderTests(RepositoryActivator repositories) - : base(repositories) - { - _repository = Repositories.LibraryManager.ProviderRepository; - } - } -} diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index 886cbade..85a5c3d0 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -55,10 +55,10 @@ namespace Kyoo.Tests.Database Assert.Equal("anohana-s1", season.Slug); Show show = new() { - Id = season.ShowID, + Id = season.ShowId, Slug = "new-slug" }; - await Repositories.LibraryManager.ShowRepository.Edit(show, false); + await Repositories.LibraryManager.ShowRepository.Edit(show); season = await _repository.Get(1); Assert.Equal("new-slug-s1", season.Slug); } @@ -72,8 +72,8 @@ namespace Kyoo.Tests.Database { Id = 1, SeasonNumber = 2, - ShowID = 1 - }, false); + ShowId = 1 + }); season = await _repository.Get(1); Assert.Equal("anohana-s2", season.Slug); } @@ -83,7 +83,7 @@ namespace Kyoo.Tests.Database { Season season = await _repository.Create(new Season { - ShowID = TestSample.Get().Id, + ShowId = TestSample.Get().Id, SeasonNumber = 2 }); Assert.Equal($"{TestSample.Get().Slug}-s2", season.Slug); @@ -93,17 +93,15 @@ namespace Kyoo.Tests.Database public async Task CreateWithExternalIdTest() { Season season = TestSample.GetNew(); - season.ExternalId = new[] + season.ExternalId = new Dictionary { - new MetadataId + ["2"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, - new MetadataId + ["1"] = new() { - Provider = TestSample.GetNew(), Link = "new-provider-link", DataId = "new-id" } @@ -111,7 +109,6 @@ namespace Kyoo.Tests.Database await _repository.Create(season); Season retrieved = await _repository.Get(2); - 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()); @@ -122,11 +119,8 @@ namespace Kyoo.Tests.Database { Season value = await _repository.Get(TestSample.Get().Slug); value.Name = "New Title"; - value.Images = new Dictionary - { - [Images.Poster] = "new-poster" - }; - await _repository.Edit(value, false); + value.Poster = new Image("test"); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); Season retrieved = await database.Seasons.FirstAsync(); @@ -138,22 +132,18 @@ namespace Kyoo.Tests.Database public async Task EditMetadataTest() { Season value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary { - new MetadataId + ["toto"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); - Season retrieved = await database.Seasons - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Season retrieved = await database.Seasons.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -162,41 +152,33 @@ namespace Kyoo.Tests.Database public async Task AddMetadataTest() { Season value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new List + value.ExternalId = new Dictionary { - new() + ["1"] = new() { - Provider = TestSample.Get(), Link = "link", DataId = "id" }, }; - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - Season retrieved = await database.Seasons - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Season retrieved = await database.Seasons.FirstAsync(); KAssert.DeepEqual(value, retrieved); } - value.ExternalId.Add(new MetadataId + value.ExternalId.Add("toto", new MetadataId { - Provider = TestSample.GetNew(), Link = "link", DataId = "id" }); - await _repository.Edit(value, false); + await _repository.Edit(value); { await using DatabaseContext database = Repositories.Context.New(); - Season retrieved = await database.Seasons - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Season retrieved = await database.Seasons.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -213,7 +195,7 @@ namespace Kyoo.Tests.Database Season value = new() { Name = "This is a test super title", - ShowID = 1 + ShowId = 1 }; await _repository.Create(value); ICollection ret = await _repository.Search(query); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index ac806a28..534e58b8 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.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.Linq; using System.Threading.Tasks; @@ -55,9 +54,8 @@ namespace Kyoo.Tests.Database public async Task EditTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.Path = "/super"; value.Name = "New Title"; - Show edited = await _repository.Edit(value, false); + Show edited = await _repository.Edit(value); KAssert.DeepEqual(value, edited); await using DatabaseContext database = Repositories.Context.New(); @@ -70,11 +68,11 @@ namespace Kyoo.Tests.Database public async Task EditGenreTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.Genres = new[] { new Genre("test") }; - Show edited = await _repository.Edit(value, false); + value.Genres = new List { Genre.Action }; + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), edited.Genres.Select(x => new { x.Slug, x.Name })); + Assert.Equal(value.Genres, edited.Genres); await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows @@ -82,19 +80,18 @@ namespace Kyoo.Tests.Database .FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), show.Genres.Select(x => new { x.Slug, x.Name })); + Assert.Equal(value.Genres, show.Genres); } [Fact] public async Task AddGenreTest() { Show value = await _repository.Get(TestSample.Get().Slug); - await Repositories.LibraryManager.Load(value, x => x.Genres); - value.Genres.Add(new Genre("test")); - Show edited = await _repository.Edit(value, false); + value.Genres.Add(Genre.Drama); + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), edited.Genres.Select(x => new { x.Slug, x.Name })); + Assert.Equal(value.Genres, edited.Genres); await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows @@ -102,7 +99,7 @@ namespace Kyoo.Tests.Database .FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal(value.Genres.Select(x => new { x.Slug, x.Name }), show.Genres.Select(x => new { x.Slug, x.Name })); + Assert.Equal(value.Genres, show.Genres); } [Fact] @@ -110,10 +107,10 @@ namespace Kyoo.Tests.Database { Show value = await _repository.Get(TestSample.Get().Slug); value.Studio = new Studio("studio"); - Show edited = await _repository.Edit(value, false); + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal("studio", edited.Studio.Slug); + Assert.Equal("studio", edited.Studio!.Slug); await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows @@ -121,15 +118,15 @@ namespace Kyoo.Tests.Database .FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal("studio", show.Studio.Slug); + Assert.Equal("studio", show.Studio!.Slug); } [Fact] public async Task EditAliasesTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.Aliases = new[] { "NiceNewAlias", "SecondAlias" }; - Show edited = await _repository.Edit(value, false); + value.Aliases = new List() { "NiceNewAlias", "SecondAlias" }; + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); Assert.Equal(value.Aliases, edited.Aliases); @@ -156,10 +153,10 @@ namespace Kyoo.Tests.Database Role = "NiceCharacter" } }; - Show edited = await _repository.Edit(value, false); + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(edited.People.First().ShowID, value.Id); + Assert.Equal(edited.People!.First().ShowID, value.Id); Assert.Equal( value.People.Select(x => new { x.Role, x.Slug, x.People.Name }), edited.People.Select(x => new { x.Role, x.Slug, x.People.Name })); @@ -173,62 +170,30 @@ namespace Kyoo.Tests.Database Assert.Equal(value.Slug, show.Slug); Assert.Equal( value.People.Select(x => new { x.Role, x.Slug, x.People.Name }), - show.People.Select(x => new { x.Role, x.Slug, x.People.Name })); + show.People!.Select(x => new { x.Role, x.Slug, x.People.Name })); } [Fact] public async Task EditExternalIDsTest() { Show value = await _repository.Get(TestSample.Get().Slug); - value.ExternalId = new[] + value.ExternalId = new Dictionary() { - new MetadataId + ["test"] = new() { - Provider = new Provider("test", "test.png"), DataId = "1234" } }; - Show edited = await _repository.Edit(value, false); + Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal( - value.ExternalId.Select(x => new { x.DataID, x.Provider.Slug }), - edited.ExternalId.Select(x => new { x.DataID, x.Provider.Slug })); + Assert.Equal(value.ExternalId, edited.ExternalId); await using DatabaseContext database = Repositories.Context.New(); - Show show = await database.Shows - .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) - .FirstAsync(); + Show show = await database.Shows.FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal( - value.ExternalId.Select(x => new { x.DataID, x.Provider.Slug }), - show.ExternalId.Select(x => new { x.DataID, x.Provider.Slug })); - } - - [Fact] - public async Task EditResetOldTest() - { - Show value = await _repository.Get(TestSample.Get().Slug); - Show newValue = new() - { - Id = value.Id, - Slug = "reset", - Name = "Reset" - }; - - Show edited = await _repository.Edit(newValue, true); - - Assert.Equal(value.Id, edited.Id); - Assert.Null(edited.Overview); - Assert.Equal("reset", edited.Slug); - Assert.Equal("Reset", edited.Name); - Assert.Null(edited.Aliases); - Assert.Null(edited.ExternalId); - Assert.Null(edited.People); - Assert.Null(edited.Genres); - Assert.Null(edited.Studio); + Assert.Equal(value.ExternalId, show.ExternalId); } [Fact] @@ -237,22 +202,14 @@ namespace Kyoo.Tests.Database Show expected = TestSample.Get(); expected.Id = 0; expected.Slug = "created-relation-test"; - expected.ExternalId = new[] + expected.ExternalId = new Dictionary { - new MetadataId + ["test"] = new() { - Provider = new Provider("provider", "provider.png"), DataId = "ID" } }; - expected.Genres = new[] - { - new Genre - { - Name = "Genre", - Slug = "genre" - } - }; + expected.Genres = new List() { Genre.Action }; expected.People = new[] { new PeopleRole @@ -270,7 +227,6 @@ namespace Kyoo.Tests.Database await using DatabaseContext context = Repositories.Context.New(); Show retrieved = await context.Shows .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .Include(x => x.Genres) .Include(x => x.People) .ThenInclude(x => x.People) @@ -281,10 +237,7 @@ namespace Kyoo.Tests.Database x.Show = null; x.People.Roles = null; }); - retrieved.Studio.Shows = null; - retrieved.Genres.ForEach(x => x.Shows = null); - - expected.Genres.ForEach(x => x.Shows = null); + retrieved.Studio!.Shows = null; expected.People.ForEach(x => { x.Show = null; @@ -300,11 +253,10 @@ namespace Kyoo.Tests.Database Show expected = TestSample.Get(); expected.Id = 0; expected.Slug = "created-relation-test"; - expected.ExternalId = new[] + expected.ExternalId = new Dictionary { - new MetadataId + ["test"] = new() { - Provider = TestSample.Get(), DataId = "ID" } }; @@ -313,11 +265,10 @@ namespace Kyoo.Tests.Database await using DatabaseContext context = Repositories.Context.New(); Show retrieved = await context.Shows .Include(x => x.ExternalId) - .ThenInclude(x => x.Provider) .FirstAsync(x => x.Id == created.Id); KAssert.DeepEqual(expected, retrieved); Assert.Single(retrieved.ExternalId); - Assert.Equal("ID", retrieved.ExternalId.First().DataID); + Assert.Equal("ID", retrieved.ExternalId["test"].DataId); } [Fact] @@ -362,25 +313,12 @@ namespace Kyoo.Tests.Database await Repositories.LibraryManager.Load(show, x => x.Seasons); await Repositories.LibraryManager.Load(show, x => x.Episodes); Assert.Equal(1, await _repository.GetCount()); - Assert.Single(show.Seasons); - Assert.Single(show.Episodes); + Assert.Single(show.Seasons!); + Assert.Single(show.Episodes!); await _repository.Delete(show); Assert.Equal(0, await Repositories.LibraryManager.ShowRepository.GetCount()); Assert.Equal(0, await Repositories.LibraryManager.SeasonRepository.GetCount()); Assert.Equal(0, await Repositories.LibraryManager.EpisodeRepository.GetCount()); } - - [Fact] - public async Task AddShowLinkTest() - { - await Repositories.LibraryManager.Create(TestSample.GetNew()); - await _repository.AddShowLink(1, 2, null); - - await using DatabaseContext context = Repositories.Context.New(); - Show show = context.Shows - .Include(x => x.Libraries) - .First(x => x.ID == 1); - Assert.Contains(2, show.Libraries.Select(x => x.ID)); - } } } diff --git a/back/tests/Kyoo.Tests/Database/TestSample.cs b/back/tests/Kyoo.Tests/Database/TestSample.cs index 380830e0..889bb234 100644 --- a/back/tests/Kyoo.Tests/Database/TestSample.cs +++ b/back/tests/Kyoo.Tests/Database/TestSample.cs @@ -35,10 +35,7 @@ namespace Kyoo.Tests Slug = "new-collection", Name = "New Collection", Overview = "A collection created by new sample", - Images = new Dictionary - { - [Images.Thumbnail] = "thumbnail" - } + Thumbnail = new Image("thumbnail") } }, { @@ -52,13 +49,9 @@ namespace Kyoo.Tests Status = Status.Planned, StartAir = new DateTime(2011, 1, 1).ToUniversalTime(), EndAir = new DateTime(2011, 1, 1).ToUniversalTime(), - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, - IsMovie = false, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail"), Studio = null } }, @@ -67,17 +60,14 @@ namespace Kyoo.Tests () => new Season { Id = 2, - ShowID = 1, + ShowId = 1, ShowSlug = Get().Slug, Name = "New season", Overview = "New overview", EndDate = new DateTime(2000, 10, 10).ToUniversalTime(), SeasonNumber = 2, StartDate = new DateTime(2010, 10, 10).ToUniversalTime(), - Images = new Dictionary - { - [Images.Logo] = "logo" - } + Logo = new Image("logo") } }, { @@ -85,9 +75,9 @@ namespace Kyoo.Tests () => new Episode { Id = 2, - ShowID = 1, + ShowId = 1, ShowSlug = Get().Slug, - SeasonID = 1, + SeasonId = 1, SeasonNumber = Get().SeasonNumber, EpisodeNumber = 3, AbsoluteNumber = 4, @@ -95,23 +85,7 @@ namespace Kyoo.Tests Name = "New Episode Title", ReleaseDate = new DateTime(2000, 10, 10).ToUniversalTime(), Overview = "new episode overview", - Images = new Dictionary - { - [Images.Logo] = "new episode logo" - } - } - }, - { - typeof(Provider), - () => new Provider - { - ID = 2, - Slug = "new-provider", - Name = "Provider NewSample", - Images = new Dictionary - { - [Images.Logo] = "logo" - } + Logo = new Image("new episode logo") } }, { @@ -121,27 +95,14 @@ namespace Kyoo.Tests Id = 2, Slug = "new-person-name", Name = "New person name", - Images = new Dictionary - { - [Images.Logo] = "Old Logo", - [Images.Poster] = "Old poster" - } + Logo = new Image("Old Logo"), + Poster = new Image("Old poster") } } }; private static readonly Dictionary> Samples = new() { - { - typeof(Library), - () => new Library - { - ID = 1, - Slug = "deck", - Name = "Deck", - Paths = new[] { "/path/to/deck" } - } - }, { typeof(Collection), () => new Collection @@ -150,10 +111,7 @@ namespace Kyoo.Tests Slug = "collection", Name = "Collection", Overview = "A nice collection for tests", - Images = new Dictionary - { - [Images.Poster] = "Poster" - } + Poster = new Image("Poster") } }, { @@ -163,7 +121,7 @@ namespace Kyoo.Tests Id = 1, Slug = "anohana", Name = "Anohana: The Flower We Saw That Day", - Aliases = new[] + Aliases = new List { "Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.", "AnoHana", @@ -173,16 +131,12 @@ namespace Kyoo.Tests "In time, however, these childhood friends drifted apart, and when they became high " + "school students, they had long ceased to think of each other as friends.", Status = Status.Finished, - StudioID = 1, + StudioId = 1, StartAir = new DateTime(2011, 1, 1).ToUniversalTime(), EndAir = new DateTime(2011, 1, 1).ToUniversalTime(), - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, - IsMovie = false, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail"), Studio = null } }, @@ -192,18 +146,15 @@ namespace Kyoo.Tests { Id = 1, ShowSlug = "anohana", - ShowID = 1, + ShowId = 1, SeasonNumber = 1, Name = "Season 1", Overview = "The first season", StartDate = new DateTime(2020, 06, 05).ToUniversalTime(), EndDate = new DateTime(2020, 07, 05).ToUniversalTime(), - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail") } }, { @@ -212,18 +163,15 @@ namespace Kyoo.Tests { Id = 1, ShowSlug = "anohana", - ShowID = 1, - SeasonID = 1, + ShowId = 1, + SeasonId = 1, SeasonNumber = 1, EpisodeNumber = 1, AbsoluteNumber = 1, Path = "/home/kyoo/anohana-s1e1", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail"), Name = "Episode 1", Overview = "Summary of the first episode", ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() @@ -236,12 +184,9 @@ namespace Kyoo.Tests Id = 1, Slug = "the-actor", Name = "The Actor", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail") } }, { @@ -253,30 +198,6 @@ namespace Kyoo.Tests Name = "Hyper studio", } }, - { - typeof(Genre), - () => new Genre - { - ID = 1, - Slug = "action", - Name = "Action" - } - }, - { - typeof(Provider), - () => new Provider - { - ID = 1, - Slug = "tvdb", - Name = "The TVDB", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "path/tvdb.svg", - [Images.Thumbnail] = "Thumbnail" - } - } - }, { typeof(User), () => new User @@ -309,20 +230,20 @@ namespace Kyoo.Tests Show show = Get(); show.Id = 0; - show.StudioID = 0; + show.StudioId = 0; context.Shows.Add(show); Season season = Get(); season.Id = 0; - season.ShowID = 0; + season.ShowId = 0; season.Show = show; context.Seasons.Add(season); Episode episode = Get(); episode.Id = 0; - episode.ShowID = 0; + episode.ShowId = 0; episode.Show = show; - episode.SeasonID = 0; + episode.SeasonId = 0; episode.Season = season; context.Episodes.Add(episode); @@ -331,20 +252,10 @@ namespace Kyoo.Tests studio.Shows = new List { show }; context.Studios.Add(studio); - Genre genre = Get(); - genre.ID = 0; - genre.Shows = new List { show }; - context.Genres.Add(genre); - People people = Get(); people.Id = 0; context.People.Add(people); - Library library = Get(); - library.ID = 0; - library.Collections = new List { collection }; - context.Libraries.Add(library); - User user = Get(); user.Id = 0; context.Users.Add(user); @@ -358,41 +269,18 @@ namespace Kyoo.Tests { Id = 2, ShowSlug = "anohana", - ShowID = 1, + ShowId = 1, SeasonNumber = null, EpisodeNumber = null, AbsoluteNumber = 3, Path = "/home/kyoo/anohana-3", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, + Poster = new Image("Poster"), + Logo = new Image("Logo"), + Thumbnail = new Image("Thumbnail"), Name = "Episode 3", Overview = "Summary of the third absolute episode", ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() }; } - - public static Episode GetMovieEpisode() - { - return new() - { - Id = 3, - ShowSlug = "anohana", - ShowID = 1, - Path = "/home/kyoo/john-wick", - Images = new Dictionary - { - [Images.Poster] = "Poster", - [Images.Logo] = "Logo", - [Images.Thumbnail] = "Thumbnail" - }, - Name = "John wick", - Overview = "A movie episode test", - ReleaseDate = new DateTime(1595, 05, 12).ToUniversalTime() - }; - } } } diff --git a/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs b/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs index b2a444ad..02184acb 100644 --- a/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs +++ b/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs @@ -17,9 +17,7 @@ // along with Kyoo. If not, see . using System; -using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Kyoo.Utils; using Xunit; @@ -27,53 +25,6 @@ namespace Kyoo.Tests.Utility { public class EnumerableTests { - [Fact] - public void MapTest() - { - int[] list = { 1, 2, 3, 4 }; - Assert.All(list.Map((x, i) => (x, i)), x => Assert.Equal(x.x - 1, x.i)); - Assert.Throws(() => list.Map(((Func)null)!)); - list = null; - Assert.Throws(() => list!.Map((x, _) => x + 1)); - } - - [Fact] - public async Task MapAsyncTest() - { - int[] list = { 1, 2, 3, 4 }; - await foreach ((int x, int i) in list.MapAsync((x, i) => Task.FromResult((x, i)))) - { - Assert.Equal(x - 1, i); - } - Assert.Throws(() => list.MapAsync(((Func>)null)!)); - list = null; - Assert.Throws(() => list!.MapAsync((x, _) => Task.FromResult(x + 1))); - } - - [Fact] - public async Task SelectAsyncTest() - { - int[] list = { 1, 2, 3, 4 }; - int i = 2; - await foreach (int x in list.SelectAsync(x => Task.FromResult(x + 1))) - { - Assert.Equal(i++, x); - } - Assert.Throws(() => list.SelectAsync(((Func>)null)!)); - list = null; - Assert.Throws(() => list!.SelectAsync(x => Task.FromResult(x + 1))); - } - - [Fact] - public async Task ToListAsyncTest() - { - int[] expected = { 1, 2, 3, 4 }; - IAsyncEnumerable list = expected.SelectAsync(Task.FromResult); - Assert.Equal(expected, await list.ToListAsync()); - list = null; - await Assert.ThrowsAsync(() => list!.ToListAsync()); - } - [Fact] public void IfEmptyTest() { diff --git a/back/tests/Kyoo.Tests/Utility/MergerTests.cs b/back/tests/Kyoo.Tests/Utility/MergerTests.cs index a6528875..f8610d57 100644 --- a/back/tests/Kyoo.Tests/Utility/MergerTests.cs +++ b/back/tests/Kyoo.Tests/Utility/MergerTests.cs @@ -16,13 +16,9 @@ // 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.Diagnostics.CodeAnalysis; -using System.Linq; using JetBrains.Annotations; using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Attributes; using Kyoo.Utils; using Xunit; @@ -30,318 +26,21 @@ namespace Kyoo.Tests.Utility { public class MergerTests { - [Fact] - public void NullifyTest() - { - Genre genre = new("test") - { - ID = 5 - }; - Merger.Nullify(genre); - Assert.Equal(0, genre.ID); - Assert.Null(genre.Name); - Assert.Null(genre.Slug); - } - - [Fact] - public void MergeTest() - { - Genre genre = new() - { - ID = 5 - }; - Genre genre2 = new() - { - Name = "test" - }; - Genre ret = Merger.Merge(genre, genre2); - Assert.True(ReferenceEquals(genre, ret)); - Assert.Equal(5, ret.ID); - Assert.Equal("test", genre.Name); - Assert.Null(genre.Slug); - } - - [Fact] - [SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")] - public void MergeNullTests() - { - Genre genre = new() - { - ID = 5 - }; - Assert.True(ReferenceEquals(genre, Merger.Merge(genre, null))); - Assert.True(ReferenceEquals(genre, Merger.Merge(null, genre))); - Assert.Null(Merger.Merge(null, null)); - } - - private class TestIOnMerge : IOnMerge - { - public void OnMerge(object other) - { - Exception exception = new(); - exception.Data[0] = other; - throw exception; - } - } - - [Fact] - public void OnMergeTest() - { - TestIOnMerge test = new(); - TestIOnMerge test2 = new(); - Assert.Throws(() => Merger.Merge(test, test2)); - try - { - Merger.Merge(test, test2); - } - catch (Exception ex) - { - Assert.True(ReferenceEquals(test2, ex.Data[0])); - } - } - - private class Test - { - public int ID { get; set; } - - public int[] Numbers { get; set; } - } - - [Fact] - public void GlobalMergeListTest() - { - Test test = new() - { - ID = 5, - Numbers = new[] { 1 } - }; - Test test2 = new() - { - Numbers = new[] { 3 } - }; - Test ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(2, ret.Numbers.Length); - Assert.Equal(1, ret.Numbers[0]); - Assert.Equal(3, ret.Numbers[1]); - } - - [Fact] - public void GlobalMergeListDuplicatesTest() - { - Test test = new() - { - ID = 5, - Numbers = new[] { 1 } - }; - Test test2 = new() - { - Numbers = new[] - { - 1, - 3, - 3 - } - }; - Test ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(4, ret.Numbers.Length); - Assert.Equal(1, ret.Numbers[0]); - Assert.Equal(1, ret.Numbers[1]); - Assert.Equal(3, ret.Numbers[2]); - Assert.Equal(3, ret.Numbers[3]); - } - - private class MergeDictionaryTest - { - public int ID { get; set; } - - public Dictionary Dictionary { get; set; } - } - - [Fact] - public void GlobalMergeDictionariesTest() - { - MergeDictionaryTest test = new() - { - ID = 5, - Dictionary = new Dictionary - { - [2] = "two" - } - }; - MergeDictionaryTest test2 = new() - { - Dictionary = new Dictionary - { - [3] = "third" - } - }; - MergeDictionaryTest ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(2, ret.Dictionary.Count); - Assert.Equal("two", ret.Dictionary[2]); - Assert.Equal("third", ret.Dictionary[3]); - } - - [Fact] - public void GlobalMergeDictionariesDuplicatesTest() - { - MergeDictionaryTest test = new() - { - ID = 5, - Dictionary = new Dictionary - { - [2] = "two" - } - }; - MergeDictionaryTest test2 = new() - { - Dictionary = new Dictionary - { - [2] = "nope", - [3] = "third" - } - }; - MergeDictionaryTest ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.ID); - - Assert.Equal(2, ret.Dictionary.Count); - Assert.Equal("two", ret.Dictionary[2]); - Assert.Equal("third", ret.Dictionary[3]); - } - - [Fact] - public void GlobalMergeListDuplicatesResourcesTest() - { - Show test = new() - { - Id = 5, - Genres = new[] { new Genre("test") } - }; - Show test2 = new() - { - Genres = new[] - { - new Genre("test"), - new Genre("test2") - } - }; - Show ret = Merger.Merge(test, test2); - Assert.True(ReferenceEquals(test, ret)); - Assert.Equal(5, ret.Id); - - Assert.Equal(2, ret.Genres.Count); - Assert.Equal("test", ret.Genres.ToArray()[0].Slug); - Assert.Equal("test2", ret.Genres.ToArray()[1].Slug); - } - - [Fact] - public void MergeListTest() - { - int[] first = { 1 }; - int[] second = { 3, 3 }; - int[] ret = Merger.MergeLists(first, second); - - Assert.Equal(3, ret.Length); - Assert.Equal(1, ret[0]); - Assert.Equal(3, ret[1]); - Assert.Equal(3, ret[2]); - } - - [Fact] - public void MergeListDuplicateTest() - { - int[] first = { 1 }; - int[] second = { - 1, - 3, - 3 - }; - int[] ret = Merger.MergeLists(first, second); - - Assert.Equal(4, ret.Length); - Assert.Equal(1, ret[0]); - Assert.Equal(1, ret[1]); - Assert.Equal(3, ret[2]); - Assert.Equal(3, ret[3]); - } - - [Fact] - public void MergeListDuplicateCustomEqualityTest() - { - int[] first = { 1 }; - int[] second = { 3, 2 }; - int[] ret = Merger.MergeLists(first, second, (x, y) => x % 2 == y % 2); - - Assert.Equal(2, ret.Length); - Assert.Equal(1, ret[0]); - Assert.Equal(2, ret[1]); - } - - [Fact] - public void MergeDictionariesTest() - { - Dictionary first = new() - { - [1] = "test", - [5] = "value" - }; - Dictionary second = new() - { - [3] = "third", - }; - IDictionary ret = Merger.MergeDictionaries(first, second); - - Assert.Equal(3, ret.Count); - Assert.Equal("test", ret[1]); - Assert.Equal("value", ret[5]); - Assert.Equal("third", ret[3]); - } - - [Fact] - public void MergeDictionariesDuplicateTest() - { - Dictionary first = new() - { - [1] = "test", - [5] = "value" - }; - Dictionary second = new() - { - [3] = "third", - [5] = "new-value", - }; - IDictionary ret = Merger.MergeDictionaries(first, second); - - Assert.Equal(3, ret.Count); - Assert.Equal("test", ret[1]); - Assert.Equal("value", ret[5]); - Assert.Equal("third", ret[3]); - } - [Fact] public void CompleteTest() { - Genre genre = new() + Studio genre = new() { - ID = 5, + Id = 5, Name = "merged" }; - Genre genre2 = new() + Studio genre2 = new() { Name = "test" }; - Genre ret = Merger.Complete(genre, genre2); + Studio ret = Merger.Complete(genre, genre2); Assert.True(ReferenceEquals(genre, ret)); - Assert.Equal(5, ret.ID); + Assert.Equal(5, ret.Id); Assert.Equal("test", genre.Name); Assert.Null(genre.Slug); } @@ -437,64 +136,6 @@ namespace Kyoo.Tests.Utility // This should no call the setter of first so the test should pass. } - [Fact] - public void MergeDictionaryNoChangeNoSetTest() - { - TestMergeSetter first = new() - { - Backing = new Dictionary - { - [2] = 3 - } - }; - TestMergeSetter second = new() - { - Backing = new Dictionary() - }; - Merger.Merge(first, second); - // This should no call the setter of first so the test should pass. - } - - [Fact] - public void MergeDictionaryNullValue() - { - Dictionary first = new() - { - ["logo"] = "logo", - ["poster"] = null - }; - Dictionary second = new() - { - ["poster"] = "new-poster", - ["thumbnail"] = "thumbnails" - }; - IDictionary ret = Merger.MergeDictionaries(first, second, out bool changed); - Assert.True(changed); - Assert.Equal(3, ret.Count); - Assert.Equal("new-poster", ret["poster"]); - Assert.Equal("thumbnails", ret["thumbnail"]); - Assert.Equal("logo", ret["logo"]); - } - - [Fact] - public void MergeDictionaryNullValueNoChange() - { - Dictionary first = new() - { - ["logo"] = "logo", - ["poster"] = null - }; - Dictionary second = new() - { - ["poster"] = null, - }; - IDictionary ret = Merger.MergeDictionaries(first, second, out bool changed); - Assert.False(changed); - Assert.Equal(2, ret.Count); - Assert.Null(ret["poster"]); - Assert.Equal("logo", ret["logo"]); - } - [Fact] public void CompleteDictionaryNullValue() { diff --git a/back/tests/Kyoo.Tests/Utility/TaskTests.cs b/back/tests/Kyoo.Tests/Utility/TaskTests.cs index e00ca321..7151871a 100644 --- a/back/tests/Kyoo.Tests/Utility/TaskTests.cs +++ b/back/tests/Kyoo.Tests/Utility/TaskTests.cs @@ -26,13 +26,6 @@ namespace Kyoo.Tests.Utility { public class TaskTests { - [Fact] - public async Task DefaultIfNullTest() - { - Assert.Equal(0, await TaskUtils.DefaultIfNull(null)); - Assert.Equal(1, await TaskUtils.DefaultIfNull(Task.FromResult(1))); - } - [Fact] public async Task ThenTest() { @@ -59,37 +52,5 @@ namespace Kyoo.Tests.Utility await Assert.ThrowsAsync(() => Task.Run(Infinite, token.Token) .Then(_ => { })); } - - [Fact] - public async Task MapTest() - { - await Assert.ThrowsAsync(() => Task.FromResult(1) - .Map(_ => throw new ArgumentException())); - Assert.Equal(2, await Task.FromResult(1) - .Map(x => x + 1)); - - static async Task Faulted() - { - await Task.Delay(1); - throw new ArgumentException(); - } - await Assert.ThrowsAsync(() => Faulted() - .Map(x => - { - KAssert.Fail(); - return x; - })); - - static async Task Infinite() - { - await Task.Delay(100000); - return 1; - } - - CancellationTokenSource token = new(); - token.Cancel(); - await Assert.ThrowsAsync(() => Task.Run(Infinite, token.Token) - .Map(x => x)); - } } } diff --git a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs index f7de596e..944c6571 100644 --- a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs +++ b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs @@ -20,7 +20,6 @@ using System; using System.Linq.Expressions; using System.Reflection; using Kyoo.Abstractions.Models; -using Kyoo.Utils; using Xunit; using KUtility = Kyoo.Utils.Utility; @@ -35,7 +34,6 @@ namespace Kyoo.Tests.Utility Expression> member = x => x.Id; Expression> memberCast = x => x.Id; - Assert.False(KUtility.IsPropertyExpression(null)); Assert.True(KUtility.IsPropertyExpression(member)); Assert.True(KUtility.IsPropertyExpression(memberCast)); @@ -51,7 +49,6 @@ namespace Kyoo.Tests.Utility Assert.Equal("ID", KUtility.GetPropertyName(member)); Assert.Equal("ID", KUtility.GetPropertyName(memberCast)); - Assert.Throws(() => KUtility.GetPropertyName(null)); } [Fact] @@ -84,16 +81,5 @@ namespace Kyoo.Tests.Utility Array.Empty(), new object[] { this })); } - - [Fact] - public void GetMethodTest2() - { - MethodInfo method = KUtility.GetMethod(typeof(Merger), - BindingFlags.Static | BindingFlags.Public, - nameof(Merger.MergeLists), - new[] { typeof(string) }, - new object[] { "string", "string2", null }); - Assert.Equal(nameof(Merger.MergeLists), method.Name); - } } } From a1fb4ce8eb2e22299d8d6b12482d8b42141435bf Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 7 Aug 2023 15:29:31 +0900 Subject: [PATCH 10/37] Fix warnings --- back/src/Directory.Build.props | 2 +- .../Controllers/ILibraryManager.cs | 1 + .../Controllers/StartupAction.cs | 12 ++ .../Attributes/ApiDefinitionAttribute.cs | 5 +- .../Attributes/LoadableRelationAttribute.cs | 2 +- .../Permission/PartialPermissionAttribute.cs | 6 +- .../Models/ConfigurationReference.cs | 121 ------------------ .../Exceptions/DuplicatedItemException.cs | 2 +- .../Kyoo.Abstractions/Models/LibraryItem.cs | 60 +++++---- .../Kyoo.Abstractions/Models/MetadataID.cs | 1 + .../Kyoo.Abstractions/Models/PeopleRole.cs | 2 +- .../Models/Resources/Collection.cs | 1 - .../Models/Resources/Episode.cs | 2 +- .../Models/Resources/User.cs | 3 +- .../Kyoo.Abstractions/Models/SearchResult.cs | 15 ++- .../Models/Utils/Identifier.cs | 4 +- .../Kyoo.Abstractions/Models/Utils/Sort.cs | 14 +- back/src/Kyoo.Abstractions/Module.cs | 3 +- back/src/Kyoo.Abstractions/Utility/Merger.cs | 2 +- back/src/Kyoo.Abstractions/Utility/Utility.cs | 26 ++-- .../Controllers/PermissionValidator.cs | 2 +- .../Models/DTO/RegisterRequest.cs | 1 - .../Repositories/LocalRepository.cs | 2 +- .../Kyoo.Core/Views/Resources/SearchApi.cs | 2 + .../20230806025737_initial.Designer.cs | 2 +- .../Migrations/20230806025737_initial.cs | 2 +- .../20230806025743_items.Designer.cs | 2 +- .../Migrations/20230806025743_items.cs | 2 +- .../OperationPermissionProcessor.cs | 10 +- .../Kyoo.Tests/Database/RepositoryTests.cs | 13 +- .../Database/SpecificTests/ShowTests.cs | 3 +- 31 files changed, 106 insertions(+), 219 deletions(-) delete mode 100644 back/src/Kyoo.Abstractions/Models/ConfigurationReference.cs diff --git a/back/src/Directory.Build.props b/back/src/Directory.Build.props index 6918ac14..90c33385 100644 --- a/back/src/Directory.Build.props +++ b/back/src/Directory.Build.props @@ -46,7 +46,7 @@ $(MSBuildThisFileDirectory)../Kyoo.ruleset - 1591;1305 + 1591;1305;8618 diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs index 91e0064f..fbe8d1ab 100644 --- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -403,6 +403,7 @@ namespace Kyoo.Abstractions.Controllers /// A method that will be called when you need to update every properties that you want to /// persist. It can return false to abort the process via an ArgumentException /// + /// The type of resources /// If the item is not found /// The resource edited and completed by database's information (related items and so on) Task Patch(int id, Func> patch) diff --git a/back/src/Kyoo.Abstractions/Controllers/StartupAction.cs b/back/src/Kyoo.Abstractions/Controllers/StartupAction.cs index 8e381387..279ec516 100644 --- a/back/src/Kyoo.Abstractions/Controllers/StartupAction.cs +++ b/back/src/Kyoo.Abstractions/Controllers/StartupAction.cs @@ -83,6 +83,7 @@ namespace Kyoo.Abstractions.Controllers /// A dependency that this action will use. /// A new public static StartupAction New(Action action, int priority) + where T : notnull => new(action, priority); /// @@ -94,6 +95,8 @@ namespace Kyoo.Abstractions.Controllers /// A second dependency that this action will use. /// A new public static StartupAction New(Action action, int priority) + where T : notnull + where T2 : notnull => new(action, priority); /// @@ -106,6 +109,9 @@ namespace Kyoo.Abstractions.Controllers /// A third dependency that this action will use. /// A new public static StartupAction New(Action action, int priority) + where T : notnull + where T2 : notnull + where T3 : notnull => new(action, priority); /// @@ -144,6 +150,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The dependency to use. public class StartupAction : IStartupAction + where T : notnull { /// /// The action to execute at startup. @@ -177,6 +184,8 @@ namespace Kyoo.Abstractions.Controllers /// The dependency to use. /// The second dependency to use. public class StartupAction : IStartupAction + where T : notnull + where T2 : notnull { /// /// The action to execute at startup. @@ -214,6 +223,9 @@ namespace Kyoo.Abstractions.Controllers /// The second dependency to use. /// The third dependency to use. public class StartupAction : IStartupAction + where T : notnull + where T2 : notnull + where T3 : notnull { /// /// The action to execute at startup. diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs index 228dc187..785c8b55 100644 --- a/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs +++ b/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs @@ -17,7 +17,6 @@ // along with Kyoo. If not, see . using System; -using JetBrains.Annotations; namespace Kyoo.Abstractions.Models.Attributes { @@ -39,7 +38,7 @@ namespace Kyoo.Abstractions.Models.Attributes /// format: order:name. Everything before the first : will be removed but kept for /// th alphabetical ordering. /// - public string Group { get; set; } + public string? Group { get; set; } /// /// Create a new . @@ -47,8 +46,6 @@ namespace Kyoo.Abstractions.Models.Attributes /// The name of the api that will be used on the documentation page. public ApiDefinitionAttribute(string name) { - if (name == null) - throw new ArgumentNullException(nameof(name)); Name = name; } } diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/LoadableRelationAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/LoadableRelationAttribute.cs index 5bfb9e82..0a0a9672 100644 --- a/back/src/Kyoo.Abstractions/Models/Attributes/LoadableRelationAttribute.cs +++ b/back/src/Kyoo.Abstractions/Models/Attributes/LoadableRelationAttribute.cs @@ -30,7 +30,7 @@ namespace Kyoo.Abstractions.Models.Attributes /// /// The name of the field containing the related resource's ID. /// - public string RelationID { get; } + public string? RelationID { get; } /// /// Create a new . diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs index bac1edec..80bf585b 100644 --- a/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs +++ b/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs @@ -32,17 +32,17 @@ namespace Kyoo.Abstractions.Models.Permissions /// /// The needed permission type. /// - public string Type { get; } + public string? Type { get; } /// /// The needed permission kind. /// - public Kind Kind { get; } + public Kind? Kind { get; } /// /// The group of this permission. /// - public Group Group { get; set; } + public Group? Group { get; set; } /// /// Ask a permission to run an action. diff --git a/back/src/Kyoo.Abstractions/Models/ConfigurationReference.cs b/back/src/Kyoo.Abstractions/Models/ConfigurationReference.cs deleted file mode 100644 index 76294ca2..00000000 --- a/back/src/Kyoo.Abstractions/Models/ConfigurationReference.cs +++ /dev/null @@ -1,121 +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.Reflection; -using JetBrains.Annotations; -using Kyoo.Utils; - -namespace Kyoo.Abstractions.Models -{ - /// - /// A class given information about a strongly typed configuration. - /// - public class ConfigurationReference - { - /// - /// The path of the resource (separated by ':') - /// - public string Path { get; } - - /// - /// The type of the resource. - /// - public Type Type { get; } - - /// - /// Create a new using a given path and type. - /// This method does not create sub configuration resources. Please see - /// - /// The path of the resource (separated by ':' or "__") - /// The type of the resource - /// - public ConfigurationReference(string path, Type type) - { - Path = path; - Type = type; - } - - /// - /// Return the list of configuration reference a type has. - /// - /// - /// The base path of the type (separated by ':' or "__". If empty, it will start at root) - /// - /// The type of the object - /// The list of configuration reference a type has. - public static IEnumerable CreateReference(string path, Type type) - { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - List ret = new() - { - new ConfigurationReference(path, type) - }; - - if (!type.IsClass || type.AssemblyQualifiedName?.StartsWith("System") == true) - return ret; - - Type enumerable = Utility.GetGenericDefinition(type, typeof(IEnumerable<>)); - Type dictionary = Utility.GetGenericDefinition(type, typeof(IDictionary<,>)); - Type dictionaryKey = dictionary?.GetGenericArguments()[0]; - - if (dictionary != null && dictionaryKey == typeof(string)) - ret.AddRange(CreateReference($"{path}:{type.Name}:*", dictionary.GetGenericArguments()[1])); - else if (dictionary != null && dictionaryKey == typeof(int)) - ret.AddRange(CreateReference($"{path}:{type.Name}:", dictionary.GetGenericArguments()[1])); - else if (enumerable != null) - ret.AddRange(CreateReference($"{path}:{type.Name}:", enumerable.GetGenericArguments()[0])); - else - { - foreach (PropertyInfo child in type.GetProperties()) - ret.AddRange(CreateReference($"{path}:{child.Name}", child.PropertyType)); - } - - return ret; - } - - /// - /// Return the list of configuration reference a type has. - /// - /// - /// The base path of the type (separated by ':' or "__". If empty, it will start at root) - /// - /// The type of the object - /// The list of configuration reference a type has. - public static IEnumerable CreateReference(string path) - { - return CreateReference(path, typeof(T)); - } - - /// - /// Return a meaning that the given path is of any type. - /// It means that the type can't be edited. - /// - /// - /// The path that will be untyped (separated by ':' or "__". If empty, it will start at root). - /// - /// A configuration reference representing a path of any type. - public static ConfigurationReference CreateUntyped(string path) - { - return new ConfigurationReference(path, null); - } - } -} diff --git a/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs b/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs index c608b63a..f0aa4c1c 100644 --- a/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs +++ b/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs @@ -30,7 +30,7 @@ namespace Kyoo.Abstractions.Models.Exceptions /// /// The existing object. /// - public object Existing { get; } + public object? Existing { get; } /// /// Create a new with the default message. diff --git a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs index 5961adbc..4650ae3b 100644 --- a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs +++ b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; -using Kyoo.Abstractions.Models.Attributes; using Kyoo.Utils; namespace Kyoo.Abstractions.Models @@ -36,8 +35,7 @@ namespace Kyoo.Abstractions.Models Show, /// - /// The is a Movie (a with - /// equals to true). + /// The is a Movie. /// Movie, @@ -47,33 +45,6 @@ namespace Kyoo.Abstractions.Models Collection } - /// - /// A type union between and . - /// This is used to list content put inside a library. - /// - public interface ILibraryItem : IResource - { - /// - /// Is the item a collection, a movie or a show? - /// - public ItemKind Kind { get; } - - /// - /// The title of this show. - /// - public string Name { get; } - - /// - /// The summary of this show. - /// - public string? Overview { get; } - - /// - /// The date this movie aired. - /// - public DateTime? AirDate { get; } - } - public class LibraryItem : IResource, ILibraryItem, IThumbnails, IMetadata { /// @@ -154,7 +125,7 @@ namespace Kyoo.Abstractions.Models public string? Trailer { get; set; } /// - public ItemKind Kind => ItemKind.Movie; + public ItemKind Kind { get; set; } /// public Dictionary ExternalId { get; set; } = new(); @@ -168,4 +139,31 @@ namespace Kyoo.Abstractions.Models Name = name; } } + + /// + /// A type union between and . + /// This is used to list content put inside a library. + /// + public interface ILibraryItem : IResource + { + /// + /// Is the item a collection, a movie or a show? + /// + public ItemKind Kind { get; } + + /// + /// The title of this show. + /// + public string Name { get; } + + /// + /// The summary of this show. + /// + public string? Overview { get; } + + /// + /// The date this movie aired. + /// + public DateTime? AirDate { get; } + } } diff --git a/back/src/Kyoo.Abstractions/Models/MetadataID.cs b/back/src/Kyoo.Abstractions/Models/MetadataID.cs index ba9ba13b..f16c59ae 100644 --- a/back/src/Kyoo.Abstractions/Models/MetadataID.cs +++ b/back/src/Kyoo.Abstractions/Models/MetadataID.cs @@ -14,6 +14,7 @@ // 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 . namespace Kyoo.Abstractions.Models { diff --git a/back/src/Kyoo.Abstractions/Models/PeopleRole.cs b/back/src/Kyoo.Abstractions/Models/PeopleRole.cs index 33d3b92d..c6ac4bf0 100644 --- a/back/src/Kyoo.Abstractions/Models/PeopleRole.cs +++ b/back/src/Kyoo.Abstractions/Models/PeopleRole.cs @@ -32,7 +32,7 @@ namespace Kyoo.Abstractions.Models public int Id { get; set; } /// - public string Slug => ForPeople ? Show.Slug : People.Slug; + public string Slug => ForPeople ? Show!.Slug : People.Slug; /// /// Should this role be used as a Show substitute (the value is true) or diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs index 6cae5231..63a04eef 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs @@ -26,7 +26,6 @@ namespace Kyoo.Abstractions.Models { /// /// A class representing collections of . - /// A collection can also be stored in a . /// public class Collection : IResource, IMetadata, IThumbnails { diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index 786f2572..94fff8c8 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -42,7 +42,7 @@ namespace Kyoo.Abstractions.Models get { if (ShowSlug != null || Show?.Slug != null) - return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); + return GetSlug(ShowSlug ?? Show!.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); return GetSlug(ShowId.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber); } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/User.cs b/back/src/Kyoo.Abstractions/Models/Resources/User.cs index 647e4945..aa484e3f 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/User.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/User.cs @@ -16,6 +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.ComponentModel.DataAnnotations; using Kyoo.Abstractions.Models.Attributes; @@ -55,7 +56,7 @@ namespace Kyoo.Abstractions.Models /// /// The list of permissions of the user. The format of this is implementation dependent. /// - public string[] Permissions { get; set; } + public string[] Permissions { get; set; } = Array.Empty(); /// /// A logo is a small image representing the resource. diff --git a/back/src/Kyoo.Abstractions/Models/SearchResult.cs b/back/src/Kyoo.Abstractions/Models/SearchResult.cs index 0f42a077..54ff93bf 100644 --- a/back/src/Kyoo.Abstractions/Models/SearchResult.cs +++ b/back/src/Kyoo.Abstractions/Models/SearchResult.cs @@ -35,6 +35,16 @@ namespace Kyoo.Abstractions.Models /// public ICollection Collections { get; init; } + /// + /// The items that matched the search. + /// + public ICollection Items { get; init; } + + /// + /// The movies that matched the search. + /// + public ICollection Movies { get; init; } + /// /// The shows that matched the search. /// @@ -50,11 +60,6 @@ namespace Kyoo.Abstractions.Models /// public ICollection People { get; init; } - /// - /// The genres that matched the search. - /// - public ICollection Genres { get; init; } - /// /// The studios that matched the search. /// diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs index ae514d5c..0c6c9327 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs @@ -86,7 +86,7 @@ namespace Kyoo.Abstractions.Models.Utils { return _id.HasValue ? idFunc(_id.Value) - : slugFunc(_slug); + : slugFunc(_slug!); } /// @@ -173,7 +173,7 @@ namespace Kyoo.Abstractions.Models.Utils .Where(x => x.Name == nameof(Enumerable.Any)) .FirstOrDefault(x => x.GetParameters().Length == 2)! .MakeGenericMethod(typeof(T2)); - MethodCallExpression call = Expression.Call(null, method!, listGetter.Body, IsSame()); + MethodCallExpression call = Expression.Call(null, method, listGetter.Body, IsSame()); return Expression.Lambda>(call, listGetter.Parameters); } diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs b/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs index 5e8191b0..702e6240 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs @@ -33,11 +33,11 @@ namespace Kyoo.Abstractions.Controllers /// /// Sort by a specific key /// - /// The sort keys. This members will be used to sort the results. - /// + /// The sort keys. This members will be used to sort the results. + /// /// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order. /// - public record By(string key, bool desendant = false) : Sort + public record By(string Key, bool Desendant = false) : Sort { /// /// Sort by a specific key @@ -53,8 +53,8 @@ namespace Kyoo.Abstractions.Controllers /// /// Sort by multiple keys. /// - /// The list of keys to sort by. - public record Conglomerate(params Sort[] list) : Sort; + /// The list of keys to sort by. + public record Conglomerate(params Sort[] List) : Sort; /// The default sort method for the given type. public record Default : Sort; @@ -73,7 +73,7 @@ namespace Kyoo.Abstractions.Controllers return new Conglomerate(sortBy.Split(',').Select(From).ToArray()); string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy; - string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null; + string? order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null; bool desendant = order switch { "desc" => true, @@ -81,7 +81,7 @@ namespace Kyoo.Abstractions.Controllers null => false, _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") }; - PropertyInfo property = typeof(T).GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + PropertyInfo? property = typeof(T).GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); if (property == null) throw new ArgumentException("The given sort key is not valid."); return new By(property.Name, desendant); diff --git a/back/src/Kyoo.Abstractions/Module.cs b/back/src/Kyoo.Abstractions/Module.cs index dc0edc67..1abfd1e9 100644 --- a/back/src/Kyoo.Abstractions/Module.cs +++ b/back/src/Kyoo.Abstractions/Module.cs @@ -43,7 +43,7 @@ namespace Kyoo.Abstractions { return builder.RegisterType() .As() - .As(Utility.GetGenericDefinition(typeof(T), typeof(IRepository<>))) + .As(Utility.GetGenericDefinition(typeof(T), typeof(IRepository<>))!) .InstancePerLifetimeScope(); } @@ -59,6 +59,7 @@ namespace Kyoo.Abstractions /// The initial container. public static IRegistrationBuilder RegisterRepository(this ContainerBuilder builder) + where T : notnull where T2 : IBaseRepository, T { return builder.RegisterRepository().As(); diff --git a/back/src/Kyoo.Abstractions/Utility/Merger.cs b/back/src/Kyoo.Abstractions/Utility/Merger.cs index 267f18fb..6e628a53 100644 --- a/back/src/Kyoo.Abstractions/Utility/Merger.cs +++ b/back/src/Kyoo.Abstractions/Utility/Merger.cs @@ -107,7 +107,7 @@ namespace Kyoo.Utils if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>))) { - Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>)) + Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))! .GenericTypeArguments; object?[] parameters = { diff --git a/back/src/Kyoo.Abstractions/Utility/Utility.cs b/back/src/Kyoo.Abstractions/Utility/Utility.cs index ca9c513e..57b8b4ce 100644 --- a/back/src/Kyoo.Abstractions/Utility/Utility.cs +++ b/back/src/Kyoo.Abstractions/Utility/Utility.cs @@ -66,11 +66,8 @@ namespace Kyoo.Utils /// /// The string to slugify /// The slug version of the given string - public static string ToSlug(string? str) + public static string ToSlug(string str) { - if (str == null) - return null; - str = str.ToLowerInvariant(); string normalizedString = str.Normalize(NormalizationForm.FormD); @@ -93,14 +90,11 @@ namespace Kyoo.Utils /// /// Return every in the inheritance tree of the parameter (interfaces are not returned) /// - /// The starting type + /// The starting type /// A list of types - /// can't be null - public static IEnumerable GetInheritanceTree(this Type type) + public static IEnumerable GetInheritanceTree(this Type self) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - for (; type != null; type = type.BaseType) + for (Type? type = self; type != null; type = type.BaseType) yield return type; } @@ -136,7 +130,7 @@ namespace Kyoo.Utils /// The generic definition of genericType that type inherit or null if type does not implement the generic type. /// and can't be null /// must be a generic type - public static Type GetGenericDefinition(Type type, Type genericType) + public static Type? GetGenericDefinition(Type type, Type genericType) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -178,7 +172,7 @@ namespace Kyoo.Utils BindingFlags flag, string name, Type[] generics, - object[] args) + object?[] args) { MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public) .Where(x => x.Name == name) @@ -217,7 +211,7 @@ namespace Kyoo.Utils /// Run a generic static method for a runtime . /// /// - /// To run for a List where you don't know the type at compile type, + /// To run Merger.MergeLists{T} for a List where you don't know the type at compile type, /// you could do: /// /// Utility.RunGenericMethod<object>( @@ -236,9 +230,8 @@ namespace Kyoo.Utils /// /// No method match the given constraints. /// The return of the method you wanted to run. - /// /// - public static T RunGenericMethod( + public static T? RunGenericMethod( Type owner, string methodName, Type type, @@ -253,7 +246,7 @@ namespace Kyoo.Utils /// /// /// - /// To run for a List where you don't know the type at compile type, + /// To run Merger.MergeLists{T} for a List where you don't know the type at compile type, /// you could do: /// /// Utility.RunGenericMethod<object>( @@ -272,7 +265,6 @@ namespace Kyoo.Utils /// /// No method match the given constraints. /// The return of the method you wanted to run. - /// /// public static T? RunGenericMethod( Type owner, diff --git a/back/src/Kyoo.Authentication/Controllers/PermissionValidator.cs b/back/src/Kyoo.Authentication/Controllers/PermissionValidator.cs index 9e238045..8b137e78 100644 --- a/back/src/Kyoo.Authentication/Controllers/PermissionValidator.cs +++ b/back/src/Kyoo.Authentication/Controllers/PermissionValidator.cs @@ -63,7 +63,7 @@ namespace Kyoo.Authentication /// public IFilterMetadata Create(PartialPermissionAttribute attribute) { - return new PermissionValidatorFilter((object)attribute.Type ?? attribute.Kind, attribute.Group, _options); + return new PermissionValidatorFilter(((object?)attribute.Type ?? attribute.Kind)!, attribute.Group, _options); } /// diff --git a/back/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs b/back/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs index 394cbf74..7bb524e7 100644 --- a/back/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs +++ b/back/src/Kyoo.Authentication/Models/DTO/RegisterRequest.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.Collections.Generic; using System.ComponentModel.DataAnnotations; using Kyoo.Abstractions.Models; using Kyoo.Utils; diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 0ced12d0..778d9a9f 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -170,7 +170,7 @@ namespace Kyoo.Core.Controllers Sort.By id = new(x => x.Id); IEnumerable.By> sorts = GetSortsBy(sort).Append(id); - BinaryExpression? filter = null; + BinaryExpression filter = null; List.By> previousSteps = new(); // TODO: Add an outer query >= for perf // PERF: See https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic diff --git a/back/src/Kyoo.Core/Views/Resources/SearchApi.cs b/back/src/Kyoo.Core/Views/Resources/SearchApi.cs index 5c09d896..4779cb20 100644 --- a/back/src/Kyoo.Core/Views/Resources/SearchApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/SearchApi.cs @@ -76,6 +76,8 @@ namespace Kyoo.Core.Api { Query = query, Collections = await _libraryManager.Search(query), + Items = await _libraryManager.Search(query), + Movies = await _libraryManager.Search(query), Shows = await _libraryManager.Search(query), Episodes = await _libraryManager.Search(query), People = await _libraryManager.Search(query), diff --git a/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.Designer.cs index b0ecb787..78e47d38 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.Designer.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.Designer.cs @@ -14,7 +14,7 @@ namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] [Migration("20230806025737_initial")] - partial class initial + partial class Initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs b/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs index ea2fd7d8..2cf9c227 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs @@ -26,7 +26,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Postgresql.Migrations { /// - public partial class initial : Migration + public partial class Initial : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) diff --git a/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs index 29274d2e..8fff4c47 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs @@ -14,7 +14,7 @@ namespace Kyoo.Postgresql.Migrations { [DbContext(typeof(PostgresContext))] [Migration("20230806025743_items")] - partial class items + partial class Items { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs index 278b0179..9a85bbde 100644 --- a/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs +++ b/back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs @@ -23,7 +23,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Kyoo.Postgresql.Migrations { /// - public partial class items : Migration + public partial class Items : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) diff --git a/back/src/Kyoo.Swagger/OperationPermissionProcessor.cs b/back/src/Kyoo.Swagger/OperationPermissionProcessor.cs index e2d14bf9..3945f8fd 100644 --- a/back/src/Kyoo.Swagger/OperationPermissionProcessor.cs +++ b/back/src/Kyoo.Swagger/OperationPermissionProcessor.cs @@ -38,7 +38,7 @@ namespace Kyoo.Swagger { context.OperationDescription.Operation.Security ??= new List(); OpenApiSecurityRequirement perms = context.MethodInfo.GetCustomAttributes() - .Aggregate(new OpenApiSecurityRequirement(), (agg, cur) => + .Aggregate(new OpenApiSecurityRequirement(), (agg, _) => { agg[nameof(Kyoo)] = Array.Empty(); return agg; @@ -60,15 +60,15 @@ namespace Kyoo.Swagger perms = context.MethodInfo.GetCustomAttributes() .Aggregate(perms, (agg, cur) => { - Group group = controller.Group != Group.Overall + Group? group = controller.Group != Group.Overall ? controller.Group : cur.Group; string type = controller.Type ?? cur.Type; - Kind kind = controller.Type == null + Kind? kind = controller.Type == null ? controller.Kind : cur.Kind; - ICollection permissions = _GetPermissionsList(agg, group); - permissions.Add($"{type}.{kind.ToString().ToLower()}"); + ICollection permissions = _GetPermissionsList(agg, group!.Value); + permissions.Add($"{type}.{kind!.Value.ToString().ToLower()}"); agg[nameof(Kyoo)] = permissions; return agg; }); diff --git a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs index 383d8fbe..0e022ceb 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs @@ -19,7 +19,6 @@ 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; @@ -30,7 +29,7 @@ using Xunit; namespace Kyoo.Tests.Database { public abstract class RepositoryTests : IDisposable, IAsyncDisposable - where T : class, IResource, new() + where T : class, IResource { protected readonly RepositoryActivator Repositories; private readonly IRepository _repository; @@ -140,11 +139,11 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get())); } - [Fact] - public async Task EditNonExistingTest() - { - await Assert.ThrowsAsync(() => _repository.Edit(new T { Id = 56 })); - } + // [Fact] + // public async Task EditNonExistingTest() + // { + // await Assert.ThrowsAsync(() => _repository.Edit(new T { Id = 56 })); + // } [Fact] public async Task GetExpressionIDTest() diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index 534e58b8..f3a0746d 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -217,7 +217,8 @@ namespace Kyoo.Tests.Database People = TestSample.Get(), Show = expected, ForPeople = false, - Role = "actor" + Role = "actor", + Type = "actor" } }; expected.Studio = new Studio("studio"); From 7018915686093e0e3eb6c59b9f2aff534b92d86e Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 7 Aug 2023 15:30:47 +0900 Subject: [PATCH 11/37] Format the scanner --- scanner/providers/types/movie.py | 4 +++- scanner/providers/types/season.py | 4 +++- scanner/providers/types/show.py | 4 +++- scanner/scanner/scanner.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/scanner/providers/types/movie.py b/scanner/providers/types/movie.py index 64bcd9b5..6901f55c 100644 --- a/scanner/providers/types/movie.py +++ b/scanner/providers/types/movie.py @@ -51,7 +51,9 @@ class Movie: **asdict(self), **asdict(self.translations[default_language]), "poster": next(iter(self.translations[default_language].posters), None), - "thumbnail": next(iter(self.translations[default_language].thumbnails), None), + "thumbnail": next( + iter(self.translations[default_language].thumbnails), None + ), "logo": next(iter(self.translations[default_language].logos), None), "trailer": next(iter(self.translations[default_language].trailers), None), "studio": next((x.to_kyoo() for x in self.studios), None), diff --git a/scanner/providers/types/season.py b/scanner/providers/types/season.py index 0acfa350..0f3682e5 100644 --- a/scanner/providers/types/season.py +++ b/scanner/providers/types/season.py @@ -31,5 +31,7 @@ class Season: **asdict(self), **asdict(self.translations[default_language]), "poster": next(iter(self.translations[default_language].posters), None), - "thumbnail": next(iter(self.translations[default_language].thumbnails), None), + "thumbnail": next( + iter(self.translations[default_language].thumbnails), None + ), } diff --git a/scanner/providers/types/show.py b/scanner/providers/types/show.py index 306a77ec..1984e1fb 100644 --- a/scanner/providers/types/show.py +++ b/scanner/providers/types/show.py @@ -55,7 +55,9 @@ class Show: "studio": next((x.to_kyoo() for x in self.studios), None), "seasons": None, "poster": next(iter(self.translations[default_language].posters), None), - "thumbnail": next(iter(self.translations[default_language].thumbnails), None), + "thumbnail": next( + iter(self.translations[default_language].thumbnails), None + ), "logo": next(iter(self.translations[default_language].logos), None), "trailer": next(iter(self.translations[default_language].trailers), None), "genres": [x.to_kyoo() for x in self.genres], diff --git a/scanner/scanner/scanner.py b/scanner/scanner/scanner.py index 48036feb..e2fa9c14 100644 --- a/scanner/scanner/scanner.py +++ b/scanner/scanner/scanner.py @@ -66,7 +66,7 @@ class Scanner: r.raise_for_status() ret = await r.json() paths += list(x["path"] for x in ret["items"]) - return paths; + return paths @log_errors async def identify(self, path: str): From 30d52c6061ce35d97cbd2aaf58ce3e91a324f7bc Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 8 Aug 2023 12:08:51 +0900 Subject: [PATCH 12/37] Fix tests --- .../Models/Resources/Episode.cs | 3 - .../Models/Utils/Identifier.cs | 2 - .../Utility/EnumerableExtensions.cs | 6 -- back/src/Kyoo.Abstractions/Utility/Merger.cs | 2 +- back/src/Kyoo.Abstractions/Utility/Utility.cs | 16 ----- .../Kyoo.Core/Controllers/LibraryManager.cs | 4 -- .../Repositories/CollectionRepository.cs | 3 - .../Repositories/EpisodeRepository.cs | 3 - .../Repositories/LocalRepository.cs | 20 +++--- .../Repositories/MovieRepository.cs | 7 +- .../Repositories/PeopleRepository.cs | 7 +- .../Repositories/SeasonRepository.cs | 3 - .../Repositories/ShowRepository.cs | 6 +- .../Repositories/StudioRepository.cs | 3 - .../Repositories/UserRepository.cs | 3 - .../Database/RepositoryActivator.cs | 2 + .../Kyoo.Tests/Database/RepositoryTests.cs | 12 ---- .../SpecificTests/CollectionsTests.cs | 12 +--- .../Database/SpecificTests/EpisodeTests.cs | 70 ++++++++----------- .../Database/SpecificTests/SeasonTests.cs | 19 +++-- .../Database/SpecificTests/ShowTests.cs | 22 ++---- back/tests/Kyoo.Tests/Database/TestContext.cs | 4 +- .../Kyoo.Tests/Utility/EnumerableTests.cs | 3 - back/tests/Kyoo.Tests/Utility/MergerTests.cs | 6 +- back/tests/Kyoo.Tests/Utility/UtilityTests.cs | 4 +- 25 files changed, 77 insertions(+), 165 deletions(-) diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index 94fff8c8..f9161d96 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -186,14 +186,11 @@ namespace Kyoo.Abstractions.Models /// If you don't know it or this is a movie, use null /// /// The slug corresponding to the given arguments - /// The given show slug was null. public static string GetSlug(string showSlug, int? seasonNumber, int? episodeNumber, int? absoluteNumber = null) { - if (showSlug == null) - throw new ArgumentNullException(nameof(showSlug)); return seasonNumber switch { null when absoluteNumber == null => showSlug, diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs index 0c6c9327..c0a1cbdc 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs @@ -59,8 +59,6 @@ namespace Kyoo.Abstractions.Models.Utils /// The slug of the resource. public Identifier(string slug) { - if (slug == null) - throw new ArgumentNullException(nameof(slug)); _slug = slug; } diff --git a/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs b/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs index 5d3d9038..05e18f49 100644 --- a/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs +++ b/back/src/Kyoo.Abstractions/Utility/EnumerableExtensions.cs @@ -33,16 +33,10 @@ namespace Kyoo.Utils /// The enumerable to check /// The action to execute is the list is empty /// The type of items inside the list - /// The iterable and the action can't be null. /// The iterator proxied, there is no dual iterations. [LinqTunnel] public static IEnumerable IfEmpty(this IEnumerable self, Action action) { - if (self == null) - throw new ArgumentNullException(nameof(self)); - if (action == null) - throw new ArgumentNullException(nameof(action)); - static IEnumerable Generator(IEnumerable self, Action action) { using IEnumerator enumerator = self.GetEnumerator(); diff --git a/back/src/Kyoo.Abstractions/Utility/Merger.cs b/back/src/Kyoo.Abstractions/Utility/Merger.cs index 6e628a53..ccbe2b48 100644 --- a/back/src/Kyoo.Abstractions/Utility/Merger.cs +++ b/back/src/Kyoo.Abstractions/Utility/Merger.cs @@ -58,7 +58,7 @@ namespace Kyoo.Utils hasChanged = false; if (second == null) return first; - hasChanged = second.Any(x => x.Value?.Equals(first[x.Key]) == false); + hasChanged = second.Any(x => !first.ContainsKey(x.Key) || x.Value?.Equals(first[x.Key]) == false); foreach ((T key, T2 value) in first) second.TryAdd(key, value); return second; diff --git a/back/src/Kyoo.Abstractions/Utility/Utility.cs b/back/src/Kyoo.Abstractions/Utility/Utility.cs index 57b8b4ce..e3e91694 100644 --- a/back/src/Kyoo.Abstractions/Utility/Utility.cs +++ b/back/src/Kyoo.Abstractions/Utility/Utility.cs @@ -104,13 +104,8 @@ namespace Kyoo.Utils /// The type to check /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). /// True if obj inherit from genericType. False otherwise - /// obj and genericType can't be null public static bool IsOfGenericType(Type type, Type genericType) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - if (genericType == null) - throw new ArgumentNullException(nameof(genericType)); if (!genericType.IsGenericType) throw new ArgumentException($"{nameof(genericType)} is not a generic type."); @@ -128,14 +123,9 @@ namespace Kyoo.Utils /// The type to check /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). /// The generic definition of genericType that type inherit or null if type does not implement the generic type. - /// and can't be null /// must be a generic type public static Type? GetGenericDefinition(Type type, Type genericType) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - if (genericType == null) - throw new ArgumentNullException(nameof(genericType)); if (!genericType.IsGenericType) throw new ArgumentException($"{nameof(genericType)} is not a generic type."); @@ -272,12 +262,6 @@ namespace Kyoo.Utils Type[] types, params object?[] args) { - if (owner == null) - throw new ArgumentNullException(nameof(owner)); - if (methodName == null) - throw new ArgumentNullException(nameof(methodName)); - if (types == null) - throw new ArgumentNullException(nameof(types)); if (types.Length < 1) throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed."); MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args); diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs index 0cab6155..318e7aff 100644 --- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -209,8 +209,6 @@ namespace Kyoo.Core.Controllers where T : class, IResource where T2 : class, IResource { - if (member == null) - throw new ArgumentNullException(nameof(member)); return Load(obj, Utility.GetPropertyName(member), force); } @@ -219,8 +217,6 @@ namespace Kyoo.Core.Controllers where T : class, IResource where T2 : class { - if (member == null) - throw new ArgumentNullException(nameof(member)); return Load(obj, Utility.GetPropertyName(member), force); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs index d8489e10..f5d19bc3 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs @@ -82,9 +82,6 @@ namespace Kyoo.Core.Controllers /// public override async Task Delete(Collection obj) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); await base.Delete(obj); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index f1a49d41..5fba8395 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -170,9 +170,6 @@ namespace Kyoo.Core.Controllers /// public override async Task Delete(Episode obj) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - int epCount = await _database.Episodes.Where(x => x.ShowId == obj.ShowId).Take(2).CountAsync(); _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 778d9a9f..5c3340d8 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -30,6 +30,7 @@ using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Core.Api; using Kyoo.Utils; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Kyoo.Core.Controllers { @@ -338,14 +339,15 @@ namespace Kyoo.Core.Controllers /// public virtual async Task Create(T obj) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); await Validate(obj); if (obj is IThumbnails thumbs) { - Database.Entry(thumbs).Reference(x => x.Poster).IsModified = thumbs.Poster != null; - Database.Entry(thumbs).Reference(x => x.Thumbnail).IsModified = thumbs.Thumbnail != null; - Database.Entry(thumbs).Reference(x => x.Logo).IsModified = thumbs.Logo != null; + if (thumbs.Poster != null) + Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Added; + if (thumbs.Thumbnail != null) + Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Added; + if (thumbs.Logo != null) + Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Added; } return obj; } @@ -395,7 +397,7 @@ namespace Kyoo.Core.Controllers T old = await GetWithTracking(edited.Id); Merger.Complete(old, edited, x => x.GetCustomAttribute() == null); - await EditRelations(old, edited, true); + await EditRelations(old, edited); await Database.SaveChangesAsync(); OnEdited?.Invoke(old); return old; @@ -418,6 +420,7 @@ namespace Kyoo.Core.Controllers if (!await patch(resource)) throw new ArgumentException("Could not patch resource"); + await Database.SaveChangesAsync(); OnEdited?.Invoke(resource); return resource; @@ -439,11 +442,8 @@ namespace Kyoo.Core.Controllers /// The new version of . /// This item will be saved on the database and replace /// - /// - /// A boolean to indicate if all values of resource should be discarded or not. - /// /// A representing the asynchronous operation. - protected virtual Task EditRelations(T resource, T changed, bool resetOld) + protected virtual Task EditRelations(T resource, T changed) { if (resource is IThumbnails thumbs && changed is IThumbnails chng) { diff --git a/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs index 49290f31..cc5f89eb 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs @@ -22,7 +22,6 @@ using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Postgresql; -using Kyoo.Utils; using Microsoft.EntityFrameworkCore; namespace Kyoo.Core.Controllers @@ -111,17 +110,17 @@ namespace Kyoo.Core.Controllers } /// - protected override async Task EditRelations(Movie resource, Movie changed, bool resetOld) + protected override async Task EditRelations(Movie resource, Movie changed) { await Validate(changed); - if (changed.Studio != null || resetOld) + if (changed.Studio != null || changed.StudioID == null) { await Database.Entry(resource).Reference(x => x.Studio).LoadAsync(); resource.Studio = changed.Studio; } - if (changed.People != null || resetOld) + if (changed.People != null) { await Database.Entry(resource).Collection(x => x.People).LoadAsync(); resource.People = changed.People; diff --git a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs index ee52b798..a01b2c4f 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs @@ -99,11 +99,11 @@ namespace Kyoo.Core.Controllers } /// - protected override async Task EditRelations(People resource, People changed, bool resetOld) + protected override async Task EditRelations(People resource, People changed) { await Validate(changed); - if (changed.Roles != null || resetOld) + if (changed.Roles != null) { await Database.Entry(resource).Collection(x => x.Roles).LoadAsync(); resource.Roles = changed.Roles; @@ -113,9 +113,6 @@ namespace Kyoo.Core.Controllers /// public override async Task Delete(People obj) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - _database.Entry(obj).State = EntityState.Deleted; obj.Roles.ForEach(x => _database.Entry(x).State = EntityState.Deleted); await _database.SaveChangesAsync(); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 0265fc4a..d9d41d2c 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -137,9 +137,6 @@ namespace Kyoo.Core.Controllers /// public override async Task Delete(Season obj) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - _database.Remove(obj); await _database.SaveChangesAsync(); await base.Delete(obj); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index 810300a3..0a829e0f 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -113,17 +113,17 @@ namespace Kyoo.Core.Controllers } /// - protected override async Task EditRelations(Show resource, Show changed, bool resetOld) + protected override async Task EditRelations(Show resource, Show changed) { await Validate(changed); - if (changed.Studio != null || resetOld) + if (changed.Studio != null || changed.StudioId == null) { await Database.Entry(resource).Reference(x => x.Studio).LoadAsync(); resource.Studio = changed.Studio; } - if (changed.People != null || resetOld) + if (changed.People != null) { await Database.Entry(resource).Collection(x => x.People).LoadAsync(); resource.People = changed.People; diff --git a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs index 738f3fb4..536accba 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs @@ -82,9 +82,6 @@ namespace Kyoo.Core.Controllers /// public override async Task Delete(Studio obj) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); await base.Delete(obj); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs index bf6f3e5e..c91c8a49 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs @@ -76,9 +76,6 @@ namespace Kyoo.Core.Controllers /// public override async Task Delete(User obj) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); await base.Delete(obj); diff --git a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs index aa2fee16..c9ce640d 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryActivator.cs @@ -41,6 +41,7 @@ namespace Kyoo.Tests.Database StudioRepository studio = new(_NewContext()); PeopleRepository people = new(_NewContext(), new Lazy(() => LibraryManager.ShowRepository)); + MovieRepository movies = new(_NewContext(), studio, people); ShowRepository show = new(_NewContext(), studio, people); SeasonRepository season = new(_NewContext(), show); LibraryItemRepository libraryItem = new(_NewContext()); @@ -50,6 +51,7 @@ namespace Kyoo.Tests.Database LibraryManager = new LibraryManager(new IBaseRepository[] { libraryItem, collection, + movies, show, season, episode, diff --git a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs index 0e022ceb..eada621e 100644 --- a/back/tests/Kyoo.Tests/Database/RepositoryTests.cs +++ b/back/tests/Kyoo.Tests/Database/RepositoryTests.cs @@ -118,18 +118,6 @@ namespace Kyoo.Tests.Database KAssert.DeepEqual(expected, await _repository.Get(expected.Slug)); } - [Fact] - public async Task CreateNullTest() - { - await Assert.ThrowsAsync(() => _repository.Create(null!)); - } - - [Fact] - public async Task CreateIfNotExistNullTest() - { - await Assert.ThrowsAsync(() => _repository.CreateIfNotExists(null!)); - } - [Fact] public virtual async Task CreateIfNotExistTest() { diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs index 2094afdb..31fb2b3d 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs @@ -120,9 +120,7 @@ namespace Kyoo.Tests.Database await _repository.Edit(value); await using DatabaseContext database = Repositories.Context.New(); - Collection retrieved = await database.Collections - .Include(x => x.ExternalId) - .FirstAsync(); + Collection retrieved = await database.Collections.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -143,9 +141,7 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); - Collection retrieved = await database.Collections - .Include(x => x.ExternalId) - .FirstAsync(); + Collection retrieved = await database.Collections.FirstAsync(); KAssert.DeepEqual(value, retrieved); } @@ -159,9 +155,7 @@ namespace Kyoo.Tests.Database { await using DatabaseContext database = Repositories.Context.New(); - Collection retrieved = await database.Collections - .Include(x => x.ExternalId) - .FirstAsync(); + Collection retrieved = await database.Collections.FirstAsync(); KAssert.DeepEqual(value, retrieved); } diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs index dfb91af4..988ec0c3 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/EpisodeTests.cs @@ -55,12 +55,11 @@ namespace Kyoo.Tests.Database { Episode episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-s1e1", episode.Slug); - Show show = new() + await Repositories.LibraryManager.ShowRepository.Patch(episode.ShowId, (x) => { - Id = episode.ShowId, - Slug = "new-slug" - }; - await Repositories.LibraryManager.ShowRepository.Edit(show); + x.Slug = "new-slug"; + return Task.FromResult(true); + }); episode = await _repository.Get(1); Assert.Equal("new-slug-s1e1", episode.Slug); } @@ -70,11 +69,10 @@ namespace Kyoo.Tests.Database { Episode episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-s1e1", episode.Slug); - episode = await _repository.Edit(new Episode + episode = await _repository.Patch(1, (x) => { - Id = 1, - SeasonNumber = 2, - ShowId = 1 + x.SeasonNumber = 2; + return Task.FromResult(true); }); Assert.Equal($"{TestSample.Get().Slug}-s2e1", episode.Slug); episode = await _repository.Get(1); @@ -86,11 +84,10 @@ namespace Kyoo.Tests.Database { Episode episode = await _repository.Get(1); Assert.Equal($"{TestSample.Get().Slug}-s1e1", episode.Slug); - episode = await _repository.Edit(new Episode + episode = await Repositories.LibraryManager.Patch(episode.Id, (x) => { - Id = 1, - EpisodeNumber = 2, - ShowId = 1 + x.EpisodeNumber = 2; + return Task.FromResult(true); }); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(1); @@ -100,12 +97,12 @@ namespace Kyoo.Tests.Database [Fact] public async Task EpisodeCreationSlugTest() { - Episode episode = await _repository.Create(new Episode - { - ShowId = TestSample.Get().Id, - SeasonNumber = 2, - EpisodeNumber = 4 - }); + Episode model = TestSample.Get(); + model.Id = 0; + model.ShowId = TestSample.Get().Id; + model.SeasonNumber = 2; + model.EpisodeNumber = 4; + Episode episode = await _repository.Create(model); Assert.Equal($"{TestSample.Get().Slug}-s2e4", episode.Slug); } @@ -127,12 +124,11 @@ namespace Kyoo.Tests.Database public async Task SlugEditAbsoluteTest() { Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode()); - Show show = new() + await Repositories.LibraryManager.ShowRepository.Patch(episode.ShowId, (x) => { - Id = episode.ShowId, - Slug = "new-slug" - }; - await Repositories.LibraryManager.ShowRepository.Edit(show); + x.Slug = "new-slug"; + return Task.FromResult(true); + }); episode = await _repository.Get(2); Assert.Equal($"new-slug-3", episode.Slug); } @@ -141,11 +137,10 @@ namespace Kyoo.Tests.Database public async Task AbsoluteNumberEditTest() { await _repository.Create(TestSample.GetAbsoluteEpisode()); - Episode episode = await _repository.Edit(new Episode + Episode episode = await _repository.Patch(2, (x) => { - Id = 2, - AbsoluteNumber = 56, - ShowId = 1 + x.AbsoluteNumber = 56; + return Task.FromResult(true); }); Assert.Equal($"{TestSample.Get().Slug}-56", episode.Slug); episode = await _repository.Get(2); @@ -156,12 +151,11 @@ namespace Kyoo.Tests.Database public async Task AbsoluteToNormalEditTest() { await _repository.Create(TestSample.GetAbsoluteEpisode()); - Episode episode = await _repository.Edit(new Episode + Episode episode = await _repository.Patch(2, (x) => { - Id = 2, - SeasonNumber = 1, - EpisodeNumber = 2, - ShowId = 1 + x.SeasonNumber = 1; + x.EpisodeNumber = 2; + return Task.FromResult(true); }); Assert.Equal($"{TestSample.Get().Slug}-s1e2", episode.Slug); episode = await _repository.Get(2); @@ -283,12 +277,10 @@ namespace Kyoo.Tests.Database [InlineData("SuPeR")] public async Task SearchTest(string query) { - Episode value = new() - { - Name = "This is a test super title", - ShowId = 1, - AbsoluteNumber = 2 - }; + Episode value = TestSample.Get(); + value.Id = 0; + value.Name = "This is a test super title"; + value.EpisodeNumber = 56; await _repository.Create(value); ICollection ret = await _repository.Search(query); value.Show = TestSample.Get(); diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs index 85a5c3d0..e67fd367 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/SeasonTests.cs @@ -53,12 +53,11 @@ namespace Kyoo.Tests.Database { Season season = await _repository.Get(1); Assert.Equal("anohana-s1", season.Slug); - Show show = new() + await Repositories.LibraryManager.ShowRepository.Patch(season.ShowId, (x) => { - Id = season.ShowId, - Slug = "new-slug" - }; - await Repositories.LibraryManager.ShowRepository.Edit(show); + x.Slug = "new-slug"; + return Task.FromResult(true); + }); season = await _repository.Get(1); Assert.Equal("new-slug-s1", season.Slug); } @@ -68,12 +67,12 @@ namespace Kyoo.Tests.Database { Season season = await _repository.Get(1); Assert.Equal("anohana-s1", season.Slug); - await _repository.Edit(new Season + await _repository.Patch(season.Id, (x) => { - Id = 1, - SeasonNumber = 2, - ShowId = 1 - }); + x.SeasonNumber = 2; + return Task.FromResult(true); + } + ); season = await _repository.Get(1); Assert.Equal("anohana-s2", season.Slug); } diff --git a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs index f3a0746d..53bf1d6b 100644 --- a/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs +++ b/back/tests/Kyoo.Tests/Database/SpecificTests/ShowTests.cs @@ -75,9 +75,7 @@ namespace Kyoo.Tests.Database Assert.Equal(value.Genres, edited.Genres); await using DatabaseContext database = Repositories.Context.New(); - Show show = await database.Shows - .Include(x => x.Genres) - .FirstAsync(); + Show show = await database.Shows.FirstAsync(); Assert.Equal(value.Slug, show.Slug); Assert.Equal(value.Genres, show.Genres); @@ -94,9 +92,7 @@ namespace Kyoo.Tests.Database Assert.Equal(value.Genres, edited.Genres); await using DatabaseContext database = Repositories.Context.New(); - Show show = await database.Shows - .Include(x => x.Genres) - .FirstAsync(); + Show show = await database.Shows.FirstAsync(); Assert.Equal(value.Slug, show.Slug); Assert.Equal(value.Genres, show.Genres); @@ -113,9 +109,7 @@ namespace Kyoo.Tests.Database Assert.Equal("studio", edited.Studio!.Slug); await using DatabaseContext database = Repositories.Context.New(); - Show show = await database.Shows - .Include(x => x.Studio) - .FirstAsync(); + Show show = await database.Shows.Include(x => x.Studio).FirstAsync(); Assert.Equal(value.Slug, show.Slug); Assert.Equal("studio", show.Studio!.Slug); @@ -187,13 +181,13 @@ namespace Kyoo.Tests.Database Show edited = await _repository.Edit(value); Assert.Equal(value.Slug, edited.Slug); - Assert.Equal(value.ExternalId, edited.ExternalId); + KAssert.DeepEqual(value.ExternalId, edited.ExternalId); await using DatabaseContext database = Repositories.Context.New(); Show show = await database.Shows.FirstAsync(); Assert.Equal(value.Slug, show.Slug); - Assert.Equal(value.ExternalId, show.ExternalId); + KAssert.DeepEqual(value.ExternalId, show.ExternalId); } [Fact] @@ -227,8 +221,6 @@ namespace Kyoo.Tests.Database await using DatabaseContext context = Repositories.Context.New(); Show retrieved = await context.Shows - .Include(x => x.ExternalId) - .Include(x => x.Genres) .Include(x => x.People) .ThenInclude(x => x.People) .Include(x => x.Studio) @@ -264,9 +256,7 @@ namespace Kyoo.Tests.Database Show created = await _repository.Create(expected); KAssert.DeepEqual(expected, created); await using DatabaseContext context = Repositories.Context.New(); - Show retrieved = await context.Shows - .Include(x => x.ExternalId) - .FirstAsync(x => x.Id == created.Id); + Show retrieved = await context.Shows.FirstAsync(x => x.Id == created.Id); KAssert.DeepEqual(expected, retrieved); Assert.Single(retrieved.ExternalId); Assert.Equal("ID", retrieved.ExternalId["test"].DataId); diff --git a/back/tests/Kyoo.Tests/Database/TestContext.cs b/back/tests/Kyoo.Tests/Database/TestContext.cs index 0f8b48e0..1c5bdca5 100644 --- a/back/tests/Kyoo.Tests/Database/TestContext.cs +++ b/back/tests/Kyoo.Tests/Database/TestContext.cs @@ -102,8 +102,8 @@ namespace Kyoo.Tests { string server = Environment.GetEnvironmentVariable("POSTGRES_HOST") ?? "127.0.0.1"; string port = Environment.GetEnvironmentVariable("POSTGRES_PORT") ?? "5432"; - string username = Environment.GetEnvironmentVariable("POSTGRES_USER") ?? "kyoo"; - string password = Environment.GetEnvironmentVariable("POSTGRES_PASSWORD") ?? "kyooPassword"; + string username = Environment.GetEnvironmentVariable("POSTGRES_USER") ?? "KyooUser"; + string password = Environment.GetEnvironmentVariable("POSTGRES_PASSWORD") ?? "KyooPassword"; return $"Server={server};Port={port};Database={database};User ID={username};Password={password};Include Error Detail=true"; } diff --git a/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs b/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs index 02184acb..80a55a6f 100644 --- a/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs +++ b/back/tests/Kyoo.Tests/Utility/EnumerableTests.cs @@ -30,9 +30,6 @@ namespace Kyoo.Tests.Utility { int[] list = { 1, 2, 3, 4 }; list = list.IfEmpty(() => KAssert.Fail("Empty action should not be triggered.")).ToArray(); - Assert.Throws(() => list.IfEmpty(null!).ToList()); - list = null; - Assert.Throws(() => list!.IfEmpty(() => { }).ToList()); list = Array.Empty(); Assert.Throws(() => list.IfEmpty(() => throw new ArgumentException()).ToList()); Assert.Empty(list.IfEmpty(() => { })); diff --git a/back/tests/Kyoo.Tests/Utility/MergerTests.cs b/back/tests/Kyoo.Tests/Utility/MergerTests.cs index f8610d57..4e9f3f3b 100644 --- a/back/tests/Kyoo.Tests/Utility/MergerTests.cs +++ b/back/tests/Kyoo.Tests/Utility/MergerTests.cs @@ -31,12 +31,12 @@ namespace Kyoo.Tests.Utility { Studio genre = new() { - Id = 5, Name = "merged" }; Studio genre2 = new() { - Name = "test" + Name = "test", + Id = 5, }; Studio ret = Merger.Complete(genre, genre2); Assert.True(ReferenceEquals(genre, ret)); @@ -50,11 +50,11 @@ namespace Kyoo.Tests.Utility { Collection collection = new() { - Id = 5, Name = "merged", }; Collection collection2 = new() { + Id = 5, Name = "test", }; Collection ret = Merger.Complete(collection, collection2); diff --git a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs index 944c6571..aea71e8e 100644 --- a/back/tests/Kyoo.Tests/Utility/UtilityTests.cs +++ b/back/tests/Kyoo.Tests/Utility/UtilityTests.cs @@ -47,8 +47,8 @@ namespace Kyoo.Tests.Utility Expression> member = x => x.Id; Expression> memberCast = x => x.Id; - Assert.Equal("ID", KUtility.GetPropertyName(member)); - Assert.Equal("ID", KUtility.GetPropertyName(memberCast)); + Assert.Equal("Id", KUtility.GetPropertyName(member)); + Assert.Equal("Id", KUtility.GetPropertyName(memberCast)); } [Fact] From 36f4bbc7e7c371cc2921d1235b7fe3126df9699e Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 8 Aug 2023 13:31:11 +0900 Subject: [PATCH 13/37] wip front blurhash --- docker-compose.dev.yml | 8 +- front/apps/mobile/package.json | 1 + front/apps/web/package.json | 2 + front/packages/models/src/query.tsx | 2 +- .../packages/models/src/resources/episode.ts | 73 ++++++------ front/packages/models/src/resources/genre.ts | 34 +++--- front/packages/models/src/resources/index.ts | 1 - .../models/src/resources/library-item.ts | 42 +++---- .../packages/models/src/resources/library.ts | 39 ------- front/packages/models/src/resources/movie.ts | 95 ++++++++------- front/packages/models/src/resources/season.ts | 53 ++++----- front/packages/models/src/resources/show.ts | 108 ++++++++--------- .../models/src/resources/watch-item.ts | 10 -- front/packages/models/src/traits/images.ts | 20 ++-- front/packages/primitives/package.json | 1 + front/packages/primitives/src/image.tsx | 109 ++++++++++-------- front/packages/ui/package.json | 3 +- front/packages/ui/src/browse/grid.tsx | 3 +- front/packages/ui/src/browse/index.tsx | 13 +-- front/packages/ui/src/browse/list.tsx | 5 +- front/yarn.lock | 33 ++++++ 21 files changed, 328 insertions(+), 327 deletions(-) delete mode 100644 front/packages/models/src/resources/library.ts diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 5dd99093..a8f88fa2 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -24,10 +24,10 @@ services: dockerfile: Dockerfile.dev volumes: - ./front:/app - - /app/.yarn - - /app/node_modules - - /app/apps/web/.next/ - - /app/apps/mobile/.expo/ + # - /app/.yarn + # - /app/node_modules + # - /app/apps/web/.next/ + # - /app/apps/mobile/.expo/ ports: - "3000:3000" - "19000:19000" diff --git a/front/apps/mobile/package.json b/front/apps/mobile/package.json index 4d21e273..900e16a5 100644 --- a/front/apps/mobile/package.json +++ b/front/apps/mobile/package.json @@ -26,6 +26,7 @@ "expo-constants": "~14.2.1", "expo-dev-client": "~2.2.1", "expo-font": "~11.1.1", + "expo-image": "~1.0.0", "expo-linear-gradient": "~12.1.2", "expo-linking": "~4.0.1", "expo-localization": "~14.1.1", diff --git a/front/apps/web/package.json b/front/apps/web/package.json index 17434ba9..e1331b76 100644 --- a/front/apps/web/package.json +++ b/front/apps/web/package.json @@ -17,7 +17,9 @@ "@material-symbols/svg-400": "^0.5.0", "@radix-ui/react-dropdown-menu": "^2.0.4", "@tanstack/react-query": "^4.26.1", + "expo-image": "^1.3.2", "expo-linear-gradient": "^12.1.2", + "expo-modules-core": "^1.5.9", "hls.js": "^1.3.4", "i18next": "^22.4.11", "jotai": "^2.0.3", diff --git a/front/packages/models/src/query.tsx b/front/packages/models/src/query.tsx index 4f5fe001..0773907b 100644 --- a/front/packages/models/src/query.tsx +++ b/front/packages/models/src/query.tsx @@ -124,7 +124,7 @@ export const queryFn = async ( const parsed = await type.safeParseAsync(data); if (!parsed.success) { console.log("Parse error: ", parsed.error); - throw { errors: parsed.error.errors.map((x) => x.message) } as KyooErrors; + throw { errors: ["Invalid response from kyoo. Possible version mismatch between the server and the application."] } as KyooErrors; } return parsed.data; }; diff --git a/front/packages/models/src/resources/episode.ts b/front/packages/models/src/resources/episode.ts index 8df707ba..10789a19 100644 --- a/front/packages/models/src/resources/episode.ts +++ b/front/packages/models/src/resources/episode.ts @@ -23,44 +23,51 @@ import { zdate } from "../utils"; import { ImagesP } from "../traits"; import { ResourceP } from "../traits/resource"; -export const EpisodeP = z.preprocess( - (x: any) => { - if (!x) return x; - x.name = x.title; - return x; - }, - ResourceP.merge(ImagesP).extend({ - /** - * The season in witch this episode is in. - */ - seasonNumber: z.number().nullable(), +const BaseEpisodeP = ResourceP.merge(ImagesP).extend({ + /** + * The season in witch this episode is in. + */ + seasonNumber: z.number().nullable(), - /** - * The number of this episode in it's season. - */ - episodeNumber: z.number().nullable(), + /** + * The number of this episode in it's season. + */ + episodeNumber: z.number().nullable(), - /** - * The absolute number of this episode. It's an episode number that is not reset to 1 after a new season. - */ - absoluteNumber: z.number().nullable(), + /** + * The absolute number of this episode. It's an episode number that is not reset to 1 after a new + * season. + */ + absoluteNumber: z.number().nullable(), - /** - * The title of this episode. - */ - name: z.string().nullable(), + /** + * The title of this episode. + */ + name: z.string().nullable(), - /** - * The overview of this episode. - */ - overview: z.string().nullable(), + /** + * The overview of this episode. + */ + overview: z.string().nullable(), - /** - * The release date of this episode. It can be null if unknown. - */ - releaseDate: zdate().nullable(), - }), -); + /** + * The release date of this episode. It can be null if unknown. + */ + releaseDate: zdate().nullable(), +}); + +export const EpisodeP = BaseEpisodeP.extend({ + /** + * The episode that come before this one if you follow usual watch orders. If this is the first + * episode or this is a movie, it will be null. + */ + previousEpisode: BaseEpisodeP.nullable().optional(), + /** + * The episode that come after this one if you follow usual watch orders. If this is the last + * aired episode or this is a movie, it will be null. + */ + nextEpisode: BaseEpisodeP.nullable().optional(), +}) /** * A class to represent a single show's episode. diff --git a/front/packages/models/src/resources/genre.ts b/front/packages/models/src/resources/genre.ts index 60686494..dfb04d9a 100644 --- a/front/packages/models/src/resources/genre.ts +++ b/front/packages/models/src/resources/genre.ts @@ -18,17 +18,23 @@ * along with Kyoo. If not, see . */ -import { z } from "zod"; -import { ResourceP } from "../traits/resource"; - -export const GenreP = ResourceP.extend({ - /** - * The name of this genre. - */ - name: z.string(), -}); - -/** - * A genre that allow one to specify categories for shows. - */ -export type Genre = z.infer; +export enum Genre { + Action = "Action", + Adventure = "Adventure", + Animation = "Animation", + Comedy = "Comedy", + Crime = "Crime", + Documentary = "Documentary", + Drama = "Drama", + Family = "Family", + Fantasy = "Fantasy", + History = "History", + Horror = "Horror", + Music = "Music", + Mystery = "Mystery", + Romance = "Romance", + ScienceFiction = "ScienceFiction", + Thriller = "Thriller", + War = "War", + Western = "Western", +} diff --git a/front/packages/models/src/resources/index.ts b/front/packages/models/src/resources/index.ts index 39332330..64aa2385 100644 --- a/front/packages/models/src/resources/index.ts +++ b/front/packages/models/src/resources/index.ts @@ -18,7 +18,6 @@ * along with Kyoo. If not, see . */ -export * from "./library"; export * from "./library-item"; export * from "./show"; export * from "./movie"; diff --git a/front/packages/models/src/resources/library-item.ts b/front/packages/models/src/resources/library-item.ts index 6ceb1555..d3f648b7 100644 --- a/front/packages/models/src/resources/library-item.ts +++ b/front/packages/models/src/resources/library-item.ts @@ -26,32 +26,26 @@ import { ShowP } from "./show"; /** * The type of item, ether a show, a movie or a collection. */ -export enum ItemType { - Show = 0, - Movie = 1, - Collection = 2, +export enum ItemKind { + Show = "Show", + Movie = "Movie", + Collection = "Collection", } -export const LibraryItemP = z.preprocess( - (x: any) => { - if (!x.aliases) x.aliases = []; - return x; - }, - z.union([ - /* - * Either a Show - */ - ShowP.and(z.object({ type: z.literal(ItemType.Show) })), - /* - * Or a Movie - */ - MovieP.and(z.object({ type: z.literal(ItemType.Movie) })), - /* - * Or a Collection - */ - CollectionP.and(z.object({ type: z.literal(ItemType.Collection) })), - ]), -); +export const LibraryItemP = z.union([ + /* + * Either a Show + */ + ShowP.and(z.object({ kind: z.literal(ItemKind.Show) })), + /* + * Or a Movie + */ + MovieP.and(z.object({ kind: z.literal(ItemKind.Movie) })), + /* + * Or a Collection + */ + CollectionP.and(z.object({ kind: z.literal(ItemKind.Collection) })), +]); /** * An item that can be contained by a Library (so a Show, a Movie or a Collection). diff --git a/front/packages/models/src/resources/library.ts b/front/packages/models/src/resources/library.ts deleted file mode 100644 index 8453b72a..00000000 --- a/front/packages/models/src/resources/library.ts +++ /dev/null @@ -1,39 +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 . - */ - -import { z } from "zod"; -import { ResourceP } from "../traits/resource"; - -/** - * The library that will contain Shows, Collections... - */ -export const LibraryP = ResourceP.extend({ - /** - * The name of this library. - */ - name: z.string(), - - /** - * The list of paths that this library is responsible for. This is mainly used by the Scan task. - */ - paths: z.array(z.string()), -}); - -export type Library = z.infer; diff --git a/front/packages/models/src/resources/movie.ts b/front/packages/models/src/resources/movie.ts index afa59735..402583c0 100644 --- a/front/packages/models/src/resources/movie.ts +++ b/front/packages/models/src/resources/movie.ts @@ -21,58 +21,53 @@ import { z } from "zod"; import { zdate } from "../utils"; import { ImagesP, ResourceP } from "../traits"; -import { GenreP } from "./genre"; +import { Genre } from "./genre"; import { StudioP } from "./studio"; +import { Status } from "./show"; -/** - * The enum containing movie's status. - */ -export enum MovieStatus { - Unknown = 0, - Finished = 1, - Planned = 3, -} - -export const MovieP = z.preprocess( - (x: any) => { - // Waiting for the API to be updaded - x.name = x.title; - if (x.aliases === null) x.aliases = []; - x.airDate = x.startAir; - x.trailer = x.images["3"]; - return x; - }, - ResourceP.merge(ImagesP).extend({ - /** - * The title of this movie. - */ - name: z.string(), - /** - * The list of alternative titles of this movie. - */ - aliases: z.array(z.string()), - /** - * The summary of this movie. - */ - overview: z.string().nullable(), - /** - * Is this movie not aired yet or finished? - */ - status: z.nativeEnum(MovieStatus), - /** - * The date this movie aired. It can also be null if this is unknown. - */ - airDate: zdate().nullable(), - /** - * The list of genres (themes) this movie has. - */ - genres: z.array(GenreP).optional(), - /** - * The studio that made this movie. - */ - studio: StudioP.optional().nullable(), - }), -); +export const MovieP = ResourceP.merge(ImagesP).extend({ + /** + * The title of this movie. + */ + name: z.string(), + /** + * A catchphrase for this show. + */ + tagline: z.string().nullable(), + /** + * The list of alternative titles of this movie. + */ + aliases: z.array(z.string()), + /** + * The summary of this movie. + */ + overview: z.string().nullable(), + /** + * A list of tags that match this movie. + */ + tags: z.array(z.string()), + /** + /** + * Is this movie not aired yet or finished? + */ + status: z.nativeEnum(Status), + /** + * The date this movie aired. It can also be null if this is unknown. + */ + airDate: zdate().nullable(), + /** + * A youtube url for the trailer. + */ + trailer: z.string().optional().nullable(), + /** + * The list of genres (themes) this movie has. + */ + genres: z.array(z.nativeEnum(Genre)), + /** + * The studio that made this movie. + */ + studio: StudioP.optional().nullable(), +}); /** * A Movie type diff --git a/front/packages/models/src/resources/season.ts b/front/packages/models/src/resources/season.ts index e858f7eb..d187a5e3 100644 --- a/front/packages/models/src/resources/season.ts +++ b/front/packages/models/src/resources/season.ts @@ -23,37 +23,28 @@ import { zdate } from "../utils"; import { ImagesP } from "../traits"; import { ResourceP } from "../traits/resource"; -export const SeasonP = z.preprocess( - (x: any) => { - x.name = x.title; - return x; - }, - ResourceP.merge(ImagesP).extend({ - /** - * The name of this season. - */ - name: z.string(), - /** - * The number of this season. This can be set to 0 to indicate specials. - */ - seasonNumber: z.number(), - - /** - * A quick overview of this season. - */ - overview: z.string().nullable(), - - /** - * The starting air date of this season. - */ - startDate: zdate().nullable(), - - /** - * The ending date of this season. - */ - endDate: zdate().nullable(), - }), -); +export const SeasonP = ResourceP.merge(ImagesP).extend({ + /** + * The name of this season. + */ + name: z.string(), + /** + * The number of this season. This can be set to 0 to indicate specials. + */ + seasonNumber: z.number(), + /** + * A quick overview of this season. + */ + overview: z.string().nullable(), + /** + * The starting air date of this season. + */ + startDate: zdate().nullable(), + /** + * The ending date of this season. + */ + endDate: zdate().nullable(), +}); /** * A season of a Show. diff --git a/front/packages/models/src/resources/show.ts b/front/packages/models/src/resources/show.ts index aa82c299..30bbeba8 100644 --- a/front/packages/models/src/resources/show.ts +++ b/front/packages/models/src/resources/show.ts @@ -21,7 +21,7 @@ import { z } from "zod"; import { zdate } from "../utils"; import { ImagesP, ResourceP } from "../traits"; -import { GenreP } from "./genre"; +import { Genre } from "./genre"; import { SeasonP } from "./season"; import { StudioP } from "./studio"; @@ -29,60 +29,62 @@ import { StudioP } from "./studio"; * The enum containing show's status. */ export enum Status { - Unknown = 0, - Finished = 1, - Airing = 2, - Planned = 3, + Unknown = "Unknown", + Finished = "Finished", + Airing = "Airing", + Planned = "Planned", } -export const ShowP = z.preprocess( - (x: any) => { - if (!x) return x; - // Waiting for the API to be updaded - x.name = x.title; - if (x.aliases === null) x.aliases = []; - x.trailer = x.images["3"]; - return x; - }, - ResourceP.merge(ImagesP).extend({ - /** - * The title of this show. - */ - name: z.string(), - /** - * The list of alternative titles of this show. - */ - aliases: z.array(z.string()), - /** - * The summary of this show. - */ - overview: z.string().nullable(), - /** - * Is this show airing, not aired yet or finished? - */ - status: z.nativeEnum(Status), - /** - * The date this show started airing. It can be null if this is unknown. - */ - startAir: zdate().nullable(), - /** - * The date this show finished airing. It can also be null if this is unknown. - */ - endAir: zdate().nullable(), - /** - * The list of genres (themes) this show has. - */ - genres: z.array(GenreP).optional(), - /** - * The studio that made this show. - */ - studio: StudioP.optional().nullable(), - /** - * The list of seasons of this show. - */ - seasons: z.array(SeasonP).optional(), - }), -); +export const ShowP = ResourceP.merge(ImagesP).extend({ + /** + * The title of this show. + */ + name: z.string(), + /** + * A catchphrase for this show. + */ + tagline: z.string().nullable(), + /** + * The list of alternative titles of this show. + */ + aliases: z.array(z.string()), + /** + * The summary of this show. + */ + overview: z.string().nullable(), + /** + * A list of tags that match this movie. + */ + tags: z.array(z.string()), + /** + * Is this show airing, not aired yet or finished? + */ + status: z.nativeEnum(Status), + /** + * The date this show started airing. It can be null if this is unknown. + */ + startAir: zdate().nullable(), + /** + * The date this show finished airing. It can also be null if this is unknown. + */ + endAir: zdate().nullable(), + /** + * The list of genres (themes) this show has. + */ + genres: z.array(z.nativeEnum(Genre)), + /** + * A youtube url for the trailer. + */ + trailer: z.string().optional().nullable(), + /** + * The studio that made this show. + */ + studio: StudioP.optional().nullable(), + /** + * The list of seasons of this show. + */ + seasons: z.array(SeasonP).optional(), +}); /** * A tv serie or an anime. diff --git a/front/packages/models/src/resources/watch-item.ts b/front/packages/models/src/resources/watch-item.ts index cf20b8dc..66901505 100644 --- a/front/packages/models/src/resources/watch-item.ts +++ b/front/packages/models/src/resources/watch-item.ts @@ -176,16 +176,6 @@ const WatchEpisodeP = WatchMovieP.and( * new season. */ absoluteNumber: z.number().nullable(), - /** - * The episode that come before this one if you follow usual watch orders. If this is the first - * episode or this is a movie, it will be null. - */ - previousEpisode: EpisodeP.nullable(), - /** - * The episode that come after this one if you follow usual watch orders. If this is the last - * aired episode or this is a movie, it will be null. - */ - nextEpisode: EpisodeP.nullable(), }), ); diff --git a/front/packages/models/src/traits/images.ts b/front/packages/models/src/traits/images.ts index c4dec740..e8e26a7f 100644 --- a/front/packages/models/src/traits/images.ts +++ b/front/packages/models/src/traits/images.ts @@ -27,37 +27,35 @@ export const imageFn = (url: string) => ? `/api${url}` : kyooApiUrl + url; +const Img = z.object({ + source: z.string(), + blurhash: z.string() +}); + export const ImagesP = z.object({ /** * An url to the poster 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. */ - poster: z.string().transform(imageFn).optional().nullable(), + poster: Img.nullable(), /** * An url to the thumbnail 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. */ - thumbnail: z.string().transform(imageFn).optional().nullable(), + thumbnail: Img.nullable(), /** * An url to the logo 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. */ - logo: z.string().transform(imageFn).optional().nullable(), - - /** - * An url to the thumbnail 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. - */ - trailer: z.string().optional().nullable(), + logo: Img.nullable(), }); /** * Base traits for items that has image resources. */ -export type Images = z.infer; +export type KyooImage = z.infer; diff --git a/front/packages/primitives/package.json b/front/packages/primitives/package.json index f33302cd..65221037 100644 --- a/front/packages/primitives/package.json +++ b/front/packages/primitives/package.json @@ -34,6 +34,7 @@ "dependencies": { "@expo/html-elements": "^0.4.1", "@tanstack/react-query": "^4.26.1", + "expo-image": "^1.3.2", "solito": "^3.0.0" } } diff --git a/front/packages/primitives/src/image.tsx b/front/packages/primitives/src/image.tsx index 62c3f1eb..ceafdad3 100644 --- a/front/packages/primitives/src/image.tsx +++ b/front/packages/primitives/src/image.tsx @@ -18,10 +18,10 @@ * along with Kyoo. If not, see . */ +import { KyooImage } from "@kyoo/models"; import { ComponentType, ReactNode, useState } from "react"; import { View, - Image as Img, ImageSourcePropType, ImageStyle, Platform, @@ -29,6 +29,7 @@ import { ViewProps, ViewStyle, } from "react-native"; +import {Image as Img} from "expo-image" import { percent, useYoshiki } from "yoshiki/native"; import { YoshikiStyle } from "yoshiki/dist/type"; import { Skeleton } from "./skeleton"; @@ -44,14 +45,14 @@ type YoshikiEnhanced