From 3fe7c83415f20abc5f18dae18ba21b77d8d6f219 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 14 Feb 2021 00:05:55 +0100 Subject: [PATCH 01/54] Starting to rework related properties loading. (Removing lazy loading) --- Kyoo.Common/Controllers/ILibraryManager.cs | 4 ++ Kyoo.Common/Controllers/IRepository.cs | 8 +-- .../{LibraryManager.cs => ALibraryManager.cs} | 8 ++- Kyoo.Common/Kyoo.Common.csproj | 2 +- Kyoo.Common/Models/Attributes/ComposedSlug.cs | 7 ++ Kyoo.Common/Models/Resources/Episode.cs | 3 +- Kyoo.Common/Models/Resources/Season.cs | 3 +- Kyoo.Common/Utility.cs | 7 ++ Kyoo.CommonAPI/Kyoo.CommonAPI.csproj | 8 +-- Kyoo.CommonAPI/LocalRepository.cs | 9 +-- Kyoo/Controllers/LibraryManager.cs | 51 ++++++++++++++ Kyoo/Kyoo.csproj | 36 +++++----- Kyoo/Models/DatabaseContext.cs | 26 ++++---- Kyoo/Models/IdentityContext.cs | 66 ++++++++++--------- Kyoo/Startup.cs | 13 ++-- Kyoo/Tasks/ExtractMetadata.cs | 5 +- 16 files changed, 170 insertions(+), 86 deletions(-) rename Kyoo.Common/Controllers/Implementations/{LibraryManager.cs => ALibraryManager.cs} (98%) create mode 100644 Kyoo.Common/Models/Attributes/ComposedSlug.cs create mode 100644 Kyoo/Controllers/LibraryManager.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index c45cbf82..4e75a971 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -60,6 +60,10 @@ namespace Kyoo.Controllers Task GetGenre(Expression> where); Task GetStudio(Expression> where); Task GetPerson(Expression> where); + + Task Load([NotNull] T obj, Expression> member) + where T : class, IResource + where T2 : class; // Library Items relations Task> GetItemsFromLibrary(int id, diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index f741de7b..522154c3 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -33,12 +33,8 @@ namespace Kyoo.Controllers Key = ExpressionRewrite.Rewrite>(key); Descendant = descendant; - if (Key == null || - Key.Body is MemberExpression || - Key.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)Key.Body).Operand is MemberExpression) - return; - - throw new ArgumentException("The given sort key is not valid."); + if (!Utility.IsPropertyExpression(Key)) + throw new ArgumentException("The given sort key is not valid."); } public Sort(string sortBy) diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs similarity index 98% rename from Kyoo.Common/Controllers/Implementations/LibraryManager.cs rename to Kyoo.Common/Controllers/Implementations/ALibraryManager.cs index bfc3cfa6..ab99f4f8 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs @@ -6,7 +6,7 @@ using Kyoo.Models; namespace Kyoo.Controllers { - public class LibraryManager : ILibraryManager + public abstract class ALibraryManager : ILibraryManager { public ILibraryRepository LibraryRepository { get; } public ILibraryItemRepository LibraryItemRepository { get; } @@ -20,7 +20,7 @@ namespace Kyoo.Controllers public IPeopleRepository PeopleRepository { get; } public IProviderRepository ProviderRepository { get; } - public LibraryManager(ILibraryRepository libraryRepository, + public ALibraryManager(ILibraryRepository libraryRepository, ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, @@ -235,6 +235,10 @@ namespace Kyoo.Controllers return PeopleRepository.Get(where); } + public abstract Task Load(T obj, Expression> member) + where T : class, IResource + where T2 : class; + public Task> GetLibraries(Expression> where = null, Sort sort = default, Pagination page = default) diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index 8d5a57d2..8d3adcd9 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -19,7 +19,7 @@ - + diff --git a/Kyoo.Common/Models/Attributes/ComposedSlug.cs b/Kyoo.Common/Models/Attributes/ComposedSlug.cs new file mode 100644 index 00000000..a66fef71 --- /dev/null +++ b/Kyoo.Common/Models/Attributes/ComposedSlug.cs @@ -0,0 +1,7 @@ +using System; + +namespace Kyoo.Models.Attributes +{ + [AttributeUsage(AttributeTargets.Class)] + public class ComposedSlug : Attribute { } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index d596b6e1..cd7c1a24 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -4,6 +4,7 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { + [ComposedSlug] public class Episode : IResource, IOnMerge { public int ID { get; set; } @@ -28,7 +29,7 @@ namespace Kyoo.Models [JsonIgnore] public virtual IEnumerable Tracks { get; set; } public string ShowTitle => Show.Title; - public string Slug => GetSlug(Show.Slug, SeasonNumber, EpisodeNumber); + public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString(); public string Thumb { get diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 9bdd2f10..d7843e5c 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -3,6 +3,7 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { + [ComposedSlug] public class Season : IResource { [JsonIgnore] public int ID { get; set; } @@ -10,7 +11,7 @@ namespace Kyoo.Models public int SeasonNumber { get; set; } = -1; - public string Slug => $"{Show.Slug}-s{SeasonNumber}"; + public string Slug => Show != null ? $"{Show.Slug}-s{SeasonNumber}" : ID.ToString(); public string Title { get; set; } public string Overview { get; set; } public int? Year { get; set; } diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 8000f1be..01a0c366 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -17,6 +17,13 @@ namespace Kyoo { public static class Utility { + public static bool IsPropertyExpression(Expression> ex) + { + return ex == null || + ex.Body is MemberExpression || + ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression; + } + public static string ToSlug(string str) { if (str == null) diff --git a/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj b/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj index b16c5e99..3a2b6456 100644 --- a/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj +++ b/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj @@ -12,10 +12,10 @@ - - - - + + + + diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 5d81f7c4..d4197e08 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -153,6 +153,7 @@ namespace Kyoo.Controllers if (edited == null) throw new ArgumentNullException(nameof(edited)); + bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled; Database.ChangeTracker.LazyLoadingEnabled = false; try { @@ -193,7 +194,7 @@ namespace Kyoo.Controllers } finally { - Database.ChangeTracker.LazyLoadingEnabled = true; + Database.ChangeTracker.LazyLoadingEnabled = lazyLoading; } } @@ -206,7 +207,7 @@ namespace Kyoo.Controllers { if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("Resource can't have null as a slug."); - if (int.TryParse(resource.Slug, out int _)) + if (int.TryParse(resource.Slug, out int _) && typeof(T).GetCustomAttribute() == null) { try { @@ -352,7 +353,7 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(edited)); if (edited is TInternal intern) return Edit(intern, resetOld).Cast(); - TInternal obj = new TInternal(); + TInternal obj = new(); Utility.Assign(obj, edited); return base.Edit(obj, resetOld).Cast(); } @@ -365,7 +366,7 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); if (obj is TInternal intern) return Delete(intern); - TInternal item = new TInternal(); + TInternal item = new(); Utility.Assign(item, obj); return Delete(item); } diff --git a/Kyoo/Controllers/LibraryManager.cs b/Kyoo/Controllers/LibraryManager.cs new file mode 100644 index 00000000..c44e0a0c --- /dev/null +++ b/Kyoo/Controllers/LibraryManager.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Kyoo.Models; + +namespace Kyoo.Controllers +{ + public class LibaryManager : ALibraryManager + { + private readonly DatabaseContext _database; + + public LibaryManager(ILibraryRepository libraryRepository, + ILibraryItemRepository libraryItemRepository, + ICollectionRepository collectionRepository, + IShowRepository showRepository, + ISeasonRepository seasonRepository, + IEpisodeRepository episodeRepository, + ITrackRepository trackRepository, + IGenreRepository genreRepository, + IStudioRepository studioRepository, + IProviderRepository providerRepository, + IPeopleRepository peopleRepository, + DatabaseContext database) + : base(libraryRepository, + libraryItemRepository, + collectionRepository, + showRepository, + seasonRepository, + episodeRepository, + trackRepository, + genreRepository, + studioRepository, + providerRepository, + peopleRepository) + { + _database = database; + } + + public override Task Load(T obj, Expression> member) + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + if (!Utility.IsPropertyExpression(member) || member == null) + throw new ArgumentException($"{nameof(member)} is not a property."); + if (typeof(IEnumerable).IsAssignableFrom(typeof(T2))) + return _database.Entry(obj).Collection(member).LoadAsync(); + return _database.Entry(obj).Reference(member).LoadAsync(); + } + } +} \ No newline at end of file diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 5ab21e23..cb1faf12 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -20,30 +20,30 @@ - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 4730cea2..96744687 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -10,6 +11,8 @@ using Npgsql; namespace Kyoo { + //TODO disable lazy loading a provide a LoadAsync method in the library manager. + public class DatabaseContext : DbContext { public DatabaseContext(DbContextOptions options) : base(options) { } @@ -41,10 +44,10 @@ namespace Kyoo NpgsqlConnection.GlobalTypeMapper.MapEnum(); } - private readonly ValueComparer> _stringArrayComparer = - new ValueComparer>( - (l1, l2) => l1.SequenceEqual(l2), - arr => arr.Aggregate(0, (i, s) => s.GetHashCode())); + private readonly ValueComparer> _stringArrayComparer = new( + (l1, l2) => l1.SequenceEqual(l2), + arr => arr.Aggregate(0, (i, s) => s.GetHashCode()) + ); protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -266,7 +269,7 @@ namespace Kyoo } public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, - CancellationToken cancellationToken = new CancellationToken()) + CancellationToken cancellationToken = new()) { try { @@ -281,7 +284,7 @@ namespace Kyoo } } - public override async Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) + public override async Task SaveChangesAsync(CancellationToken cancellationToken = new()) { try { @@ -297,7 +300,7 @@ namespace Kyoo } public async Task SaveChangesAsync(string duplicateMessage, - CancellationToken cancellationToken = new CancellationToken()) + CancellationToken cancellationToken = new()) { try { @@ -312,7 +315,7 @@ namespace Kyoo } } - public async Task SaveIfNoDuplicates(CancellationToken cancellationToken = new CancellationToken()) + public async Task SaveIfNoDuplicates(CancellationToken cancellationToken = new()) { try { @@ -324,13 +327,12 @@ namespace Kyoo } } - public static bool IsDuplicateException(DbUpdateException ex) + private static bool IsDuplicateException(Exception ex) { - return ex.InnerException is PostgresException inner - && inner.SqlState == PostgresErrorCodes.UniqueViolation; + return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation}; } - public void DiscardChanges() + private void DiscardChanges() { foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged && x.State != EntityState.Detached)) diff --git a/Kyoo/Models/IdentityContext.cs b/Kyoo/Models/IdentityContext.cs index 7502c34d..c414efcc 100644 --- a/Kyoo/Models/IdentityContext.cs +++ b/Kyoo/Models/IdentityContext.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using System.Linq; using IdentityServer4.Models; namespace Kyoo { - public class IdentityContext + public static class IdentityContext { public static IEnumerable GetIdentityResources() { @@ -19,7 +20,7 @@ namespace Kyoo { return new List { - new Client + new() { ClientId = "kyoo.webapp", @@ -38,6 +39,38 @@ namespace Kyoo } }; } + + public static IEnumerable GetScopes() + { + return new[] + { + new ApiScope + { + Name = "kyoo.read", + DisplayName = "Read only access to the API.", + }, + new ApiScope + { + Name = "kyoo.write", + DisplayName = "Read and write access to the public API" + }, + new ApiScope + { + Name = "kyoo.play", + DisplayName = "Allow playback of movies and episodes." + }, + new ApiScope + { + Name = "kyoo.download", + DisplayName = "Allow downloading of episodes and movies from kyoo." + }, + new ApiScope + { + Name = "kyoo.admin", + DisplayName = "Full access to the admin's API and the public API." + } + }; + } public static IEnumerable GetApis() { @@ -46,34 +79,7 @@ namespace Kyoo new ApiResource { Name = "Kyoo", - Scopes = - { - new Scope - { - Name = "kyoo.read", - DisplayName = "Read only access to the API.", - }, - new Scope - { - Name = "kyoo.write", - DisplayName = "Read and write access to the public API" - }, - new Scope - { - Name = "kyoo.play", - DisplayName = "Allow playback of movies and episodes." - }, - new Scope - { - Name = "kyoo.download", - DisplayName = "Allow downloading of episodes and movies from kyoo." - }, - new Scope - { - Name = "kyoo.admin", - DisplayName = "Full access to the admin's API and the public API." - } - } + Scopes = GetScopes().Select(x => x.Name).ToArray() } }; } diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 4eaa185c..9f32b56d 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Reflection; +using IdentityServer4.Extensions; using IdentityServer4.Services; using Kyoo.Api; using Kyoo.Controllers; @@ -47,8 +48,7 @@ namespace Kyoo services.AddDbContext(options => { - options.UseLazyLoadingProxies() - .UseNpgsql(_configuration.GetConnectionString("Database")); + options.UseNpgsql(_configuration.GetConnectionString("Database")); // .EnableSensitiveDataLogging() // .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); }, ServiceLifetime.Transient); @@ -72,7 +72,6 @@ namespace Kyoo services.AddIdentityServer(options => { options.IssuerUri = publicUrl; - options.PublicOrigin = publicUrl; options.UserInteraction.LoginUrl = publicUrl + "login"; options.UserInteraction.ErrorUrl = publicUrl + "error"; options.UserInteraction.LogoutUrl = publicUrl + "logout"; @@ -92,6 +91,7 @@ namespace Kyoo options.EnableTokenCleanup = true; }) .AddInMemoryIdentityResources(IdentityContext.GetIdentityResources()) + .AddInMemoryApiScopes(IdentityContext.GetScopes()) .AddInMemoryApiResources(IdentityContext.GetApis()) .AddProfileService() .AddSigninKeys(_configuration); @@ -157,7 +157,7 @@ namespace Kyoo services.AddHostedService(provider => (TaskManager)provider.GetService()); } - public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { @@ -186,6 +186,11 @@ namespace Kyoo MinimumSameSitePolicy = SameSiteMode.Strict }); app.UseAuthentication(); + app.Use((ctx, next) => + { + ctx.SetIdentityServerOrigin(_configuration.GetValue("public_url")); + return next(); + }); app.UseIdentityServer(); app.UseAuthorization(); diff --git a/Kyoo/Tasks/ExtractMetadata.cs b/Kyoo/Tasks/ExtractMetadata.cs index f65d1b42..ac613e0e 100644 --- a/Kyoo/Tasks/ExtractMetadata.cs +++ b/Kyoo/Tasks/ExtractMetadata.cs @@ -98,10 +98,9 @@ namespace Kyoo.Tasks await _thumbnails!.Validate(episode, true); if (subs) { - // TODO this doesn't work. - IEnumerable tracks = (await _transcoder!.ExtractInfos(episode.Path)) + // TODO handle external subtites. + episode.Tracks = (await _transcoder!.ExtractInfos(episode.Path)) .Where(x => x.Type != StreamType.Font); - episode.Tracks = tracks; await _library.EditEpisode(episode, false); } } From 61eeb40c5319888d321734aaf8555d7c470c0814 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 14 Feb 2021 00:07:04 +0100 Subject: [PATCH 02/54] Fixing a typo --- Kyoo/Controllers/LibraryManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Kyoo/Controllers/LibraryManager.cs b/Kyoo/Controllers/LibraryManager.cs index c44e0a0c..b3cfbd41 100644 --- a/Kyoo/Controllers/LibraryManager.cs +++ b/Kyoo/Controllers/LibraryManager.cs @@ -2,15 +2,14 @@ using System; using System.Collections; using System.Linq.Expressions; using System.Threading.Tasks; -using Kyoo.Models; namespace Kyoo.Controllers { - public class LibaryManager : ALibraryManager + public class LibraryManager : ALibraryManager { private readonly DatabaseContext _database; - public LibaryManager(ILibraryRepository libraryRepository, + public LibraryManager(ILibraryRepository libraryRepository, ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, From 5f6c663f1891d83f8a2c18021eb3ee7d53cdf8a5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 14 Feb 2021 16:25:11 +0100 Subject: [PATCH 03/54] Making the load method of collections --- Kyoo/Controllers/LibraryManager.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Kyoo/Controllers/LibraryManager.cs b/Kyoo/Controllers/LibraryManager.cs index b3cfbd41..593b0645 100644 --- a/Kyoo/Controllers/LibraryManager.cs +++ b/Kyoo/Controllers/LibraryManager.cs @@ -1,7 +1,9 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Kyoo.Controllers { @@ -42,9 +44,14 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); if (!Utility.IsPropertyExpression(member) || member == null) throw new ArgumentException($"{nameof(member)} is not a property."); - if (typeof(IEnumerable).IsAssignableFrom(typeof(T2))) - return _database.Entry(obj).Collection(member).LoadAsync(); - return _database.Entry(obj).Reference(member).LoadAsync(); + + EntityEntry entry = _database.Entry(obj); + + if (!typeof(IEnumerable).IsAssignableFrom(typeof(T2))) + return entry.Reference(member).LoadAsync(); + + Type collectionType = Utility.GetGenericDefinition(typeof(T2), typeof(IEnumerable<>)); + return Utility.RunGenericMethod(entry, "Collection", collectionType, member).LoadAsync(); } } } \ No newline at end of file From bc7a314409592f218f04fe561b5a52b4403a1a27 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Feb 2021 00:08:36 +0100 Subject: [PATCH 04/54] Replacing IEnumearble with ICollections inside resources --- Kyoo.Common/Controllers/ILibraryManager.cs | 7 +++-- Kyoo.Common/Controllers/IMetadataProvider.cs | 4 +-- Kyoo.Common/Controllers/IProviderManager.cs | 2 +- Kyoo.Common/Kyoo.Common.csproj | 6 ++++- Kyoo.Common/Models/PeopleRole.cs | 9 ++++--- Kyoo.Common/Models/Plugin.cs | 2 +- Kyoo.Common/Models/Resources/Collection.cs | 4 +-- Kyoo.Common/Models/Resources/Episode.cs | 17 ++++-------- Kyoo.Common/Models/Resources/Genre.cs | 2 +- Kyoo.Common/Models/Resources/Library.cs | 13 +++++----- Kyoo.Common/Models/Resources/People.cs | 7 ++--- Kyoo.Common/Models/Resources/Season.cs | 7 ++--- Kyoo.Common/Models/Resources/Show.cs | 26 +++++++++---------- Kyoo.Common/Models/Resources/Studio.cs | 2 +- .../ALibraryManager.cs | 19 ++++++++------ .../LibraryManager.cs | 9 ++++--- Kyoo/Controllers/ProviderManager.cs | 4 +-- Kyoo/Kyoo.csproj | 4 +-- Kyoo/Models/DatabaseContext.cs | 2 +- Kyoo/Models/Resources/CollectionDE.cs | 8 +++--- Kyoo/Models/Resources/GenreDE.cs | 4 +-- Kyoo/Models/Resources/LibraryDE.cs | 12 ++++----- Kyoo/Models/Resources/ShowDE.cs | 12 ++++----- Kyoo/Program.cs | 2 +- Kyoo/Startup.cs | 2 +- Kyoo/Tasks/Crawler.cs | 5 ++-- Kyoo/Tasks/ExtractMetadata.cs | 4 +-- 27 files changed, 99 insertions(+), 96 deletions(-) rename {Kyoo.Common/Controllers/Implementations => Kyoo.CommonAPI}/ALibraryManager.cs (97%) rename {Kyoo/Controllers => Kyoo.CommonAPI}/LibraryManager.cs (88%) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 4e75a971..2cdbaf50 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -240,10 +240,9 @@ namespace Kyoo.Controllers Task EditGenre(Genre genre, bool resetOld); Task EditStudio(Studio studio, bool resetOld); Task EditPeople(People people, bool resetOld); - // Delete values - Task DelteLibrary(Library library); + Task DeleteLibrary(Library library); Task DeleteCollection(Collection collection); Task DeleteShow(Show show); Task DeleteSeason(Season season); @@ -254,7 +253,7 @@ namespace Kyoo.Controllers Task DeletePeople(People people); //Delete by slug - Task DelteLibrary(string slug); + Task DeleteLibrary(string slug); Task DeleteCollection(string slug); Task DeleteShow(string slug); Task DeleteSeason(string slug); @@ -265,7 +264,7 @@ namespace Kyoo.Controllers Task DeletePeople(string slug); //Delete by id - Task DelteLibrary(int id); + Task DeleteLibrary(int id); Task DeleteCollection(int id); Task DeleteShow(int id); Task DeleteSeason(int id); diff --git a/Kyoo.Common/Controllers/IMetadataProvider.cs b/Kyoo.Common/Controllers/IMetadataProvider.cs index c552d5e3..6cf8a6ac 100644 --- a/Kyoo.Common/Controllers/IMetadataProvider.cs +++ b/Kyoo.Common/Controllers/IMetadataProvider.cs @@ -11,8 +11,8 @@ namespace Kyoo.Controllers Task GetCollectionFromName(string name); Task GetShowByID(Show show); - Task> SearchShows(string showName, bool isMovie); - Task> GetPeople(Show show); + Task> SearchShows(string showName, bool isMovie); + Task> GetPeople(Show show); Task GetSeason(Show show, int seasonNumber); diff --git a/Kyoo.Common/Controllers/IProviderManager.cs b/Kyoo.Common/Controllers/IProviderManager.cs index 07f09cb2..d1136052 100644 --- a/Kyoo.Common/Controllers/IProviderManager.cs +++ b/Kyoo.Common/Controllers/IProviderManager.cs @@ -12,6 +12,6 @@ namespace Kyoo.Controllers Task> SearchShows(string showName, bool isMovie, Library library); Task GetSeason(Show show, int seasonNumber, Library library); Task GetEpisode(Show show, string episodePath, int seasonNumber, int episodeNumber, int absoluteNumber, Library library); - Task> GetPeople(Show show, Library library); + Task> GetPeople(Show show, Library library); } } \ No newline at end of file diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index 8d3adcd9..44f198b9 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -12,7 +12,7 @@ SDG GPL-3.0-or-later true - 1.0.22 + 1.0.23 true snupkg default @@ -23,4 +23,8 @@ + + + + diff --git a/Kyoo.Common/Models/PeopleRole.cs b/Kyoo.Common/Models/PeopleRole.cs index 54fe48cb..a9fc0582 100644 --- a/Kyoo.Common/Models/PeopleRole.cs +++ b/Kyoo.Common/Models/PeopleRole.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using Kyoo.Models.Attributes; @@ -33,7 +34,7 @@ namespace Kyoo.Models } [ExpressionRewrite(nameof(People) + "."+ nameof(Models.People.ExternalIDs))] - public IEnumerable ExternalIDs + public ICollection ExternalIDs { get => People.ExternalIDs; set => People.ExternalIDs = value; @@ -75,7 +76,7 @@ namespace Kyoo.Models public string Slug { get; set; } public string Title { get; set; } - public IEnumerable Aliases { get; set; } + public ICollection Aliases { get; set; } [JsonIgnore] public string Path { get; set; } public string Overview { get; set; } public Status? Status { get; set; } @@ -96,7 +97,7 @@ namespace Kyoo.Models Type = x.Type; Slug = x.Show.Slug; Title = x.Show.Title; - Aliases = x.Show.Aliases; + Aliases = x.Show.Aliases?.ToArray(); Path = x.Show.Path; Overview = x.Show.Overview; Status = x.Show.Status; @@ -116,7 +117,7 @@ namespace Kyoo.Models Type = x.Type, Slug = x.Show.Slug, Title = x.Show.Title, - Aliases = x.Show.Aliases, + Aliases = x.Show.Aliases != null ? x.Show.Aliases.ToArray() : null, Path = x.Show.Path, Overview = x.Show.Overview, Status = x.Show.Status, diff --git a/Kyoo.Common/Models/Plugin.cs b/Kyoo.Common/Models/Plugin.cs index 9965d1ba..7947d542 100644 --- a/Kyoo.Common/Models/Plugin.cs +++ b/Kyoo.Common/Models/Plugin.cs @@ -5,6 +5,6 @@ namespace Kyoo.Models public interface IPlugin { public string Name { get; } - public IEnumerable Tasks { get; } + public ICollection Tasks { get; } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 2e8c85bc..283fe017 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -10,8 +10,8 @@ namespace Kyoo.Models public string Name { get; set; } public string Poster { get; set; } public string Overview { get; set; } - [JsonIgnore] public virtual IEnumerable Shows { get; set; } - [JsonIgnore] public virtual IEnumerable Libraries { get; set; } + [JsonIgnore] public virtual ICollection Shows { get; set; } + [JsonIgnore] public virtual ICollection Libraries { get; set; } public Collection() { } diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index cd7c1a24..7a8007ee 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -24,9 +25,9 @@ namespace Kyoo.Models public int Runtime { get; set; } //This runtime variable should be in minutes [JsonIgnore] public string Poster { get; set; } - [EditableRelation] public virtual IEnumerable ExternalIDs { get; set; } + [EditableRelation] public virtual ICollection ExternalIDs { get; set; } - [JsonIgnore] public virtual IEnumerable Tracks { get; set; } + [JsonIgnore] public virtual ICollection Tracks { get; set; } public string ShowTitle => Show.Title; public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString(); @@ -61,7 +62,7 @@ namespace Kyoo.Models ReleaseDate = releaseDate; Runtime = runtime; Poster = poster; - ExternalIDs = externalIDs; + ExternalIDs = externalIDs?.ToArray(); } public Episode(int showID, @@ -76,19 +77,11 @@ namespace Kyoo.Models int runtime, string poster, IEnumerable externalIDs) + : this(seasonNumber, episodeNumber, absoluteNumber, title, overview, releaseDate, runtime, poster, externalIDs) { ShowID = showID; SeasonID = seasonID; - SeasonNumber = seasonNumber; - EpisodeNumber = episodeNumber; - AbsoluteNumber = absoluteNumber; Path = path; - Title = title; - Overview = overview; - ReleaseDate = releaseDate; - Runtime = runtime; - Poster = poster; - ExternalIDs = externalIDs; } public static string GetSlug(string showSlug, int seasonNumber, int episodeNumber) diff --git a/Kyoo.Common/Models/Resources/Genre.cs b/Kyoo.Common/Models/Resources/Genre.cs index 947a2866..16e8640d 100644 --- a/Kyoo.Common/Models/Resources/Genre.cs +++ b/Kyoo.Common/Models/Resources/Genre.cs @@ -9,7 +9,7 @@ namespace Kyoo.Models public string Slug { get; set; } public string Name { get; set; } - [JsonIgnore] public virtual IEnumerable Shows { get; set; } + [JsonIgnore] public virtual ICollection Shows { get; set; } public Genre() {} diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index 765d48d3..de3ea5fc 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -8,12 +9,12 @@ namespace Kyoo.Models [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } - public IEnumerable Paths { get; set; } + public ICollection Paths { get; set; } - [EditableRelation] public virtual IEnumerable Providers { get; set; } + [EditableRelation] public virtual ICollection Providers { get; set; } - [JsonIgnore] public virtual IEnumerable Shows { get; set; } - [JsonIgnore] public virtual IEnumerable Collections { get; set; } + [JsonIgnore] public virtual ICollection Shows { get; set; } + [JsonIgnore] public virtual ICollection Collections { get; set; } public Library() { } @@ -21,8 +22,8 @@ namespace Kyoo.Models { Slug = slug; Name = name; - Paths = paths; - Providers = providers; + Paths = paths?.ToArray(); + Providers = providers?.ToArray(); } } } diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index 5b8828b8..fe9cc4a1 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -9,9 +10,9 @@ namespace Kyoo.Models public string Slug { get; set; } public string Name { get; set; } public string Poster { get; set; } - [EditableRelation] public virtual IEnumerable ExternalIDs { get; set; } + [EditableRelation] public virtual ICollection ExternalIDs { get; set; } - [EditableRelation] [JsonReadOnly] public virtual IEnumerable Roles { get; set; } + [EditableRelation] [JsonReadOnly] public virtual ICollection Roles { get; set; } public People() {} @@ -20,7 +21,7 @@ namespace Kyoo.Models Slug = slug; Name = name; Poster = poster; - ExternalIDs = externalIDs; + ExternalIDs = externalIDs?.ToArray(); } } } diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index d7843e5c..0aee3e9a 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -17,10 +18,10 @@ namespace Kyoo.Models public int? Year { get; set; } [JsonIgnore] public string Poster { get; set; } - [EditableRelation] public virtual IEnumerable ExternalIDs { get; set; } + [EditableRelation] public virtual ICollection ExternalIDs { get; set; } [JsonIgnore] public virtual Show Show { get; set; } - [JsonIgnore] public virtual IEnumerable Episodes { get; set; } + [JsonIgnore] public virtual ICollection Episodes { get; set; } public Season() { } @@ -38,7 +39,7 @@ namespace Kyoo.Models Overview = overview; Year = year; Poster = poster; - ExternalIDs = externalIDs; + ExternalIDs = externalIDs?.ToArray(); } } } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 69bf72ec..675f45dc 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -9,7 +9,7 @@ namespace Kyoo.Models public int ID { get; set; } public string Slug { get; set; } public string Title { get; set; } - [EditableRelation] public IEnumerable Aliases { get; set; } + [EditableRelation] public ICollection Aliases { get; set; } [JsonIgnore] public string Path { get; set; } public string Overview { get; set; } public Status? Status { get; set; } @@ -24,17 +24,17 @@ namespace Kyoo.Models public bool IsMovie { get; set; } - [EditableRelation] public virtual IEnumerable ExternalIDs { get; set; } + [EditableRelation] public virtual ICollection ExternalIDs { get; set; } [JsonIgnore] public int? StudioID { get; set; } [EditableRelation] [JsonReadOnly] public virtual Studio Studio { get; set; } - [EditableRelation] [JsonReadOnly] public virtual IEnumerable Genres { get; set; } - [EditableRelation] [JsonReadOnly] public virtual IEnumerable People { get; set; } - [JsonIgnore] public virtual IEnumerable Seasons { get; set; } - [JsonIgnore] public virtual IEnumerable Episodes { get; set; } - [JsonIgnore] public virtual IEnumerable Libraries { get; set; } - [JsonIgnore] public virtual IEnumerable Collections { get; set; } + [EditableRelation] [JsonReadOnly] public virtual ICollection Genres { get; set; } + [EditableRelation] [JsonReadOnly] public virtual ICollection People { get; set; } + [JsonIgnore] public virtual ICollection Seasons { get; set; } + [JsonIgnore] public virtual ICollection Episodes { get; set; } + [JsonIgnore] public virtual ICollection Libraries { get; set; } + [JsonIgnore] public virtual ICollection Collections { get; set; } public Show() { } @@ -51,15 +51,15 @@ namespace Kyoo.Models { Slug = slug; Title = title; - Aliases = aliases; + Aliases = aliases?.ToArray(); Path = path; Overview = overview; TrailerUrl = trailerUrl; - Genres = genres; + Genres = genres?.ToArray(); Status = status; StartYear = startYear; EndYear = endYear; - ExternalIDs = externalIDs; + ExternalIDs = externalIDs?.ToArray(); } public Show(string slug, @@ -78,7 +78,7 @@ namespace Kyoo.Models { Slug = slug; Title = title; - Aliases = aliases; + Aliases = aliases?.ToArray(); Path = path; Overview = overview; TrailerUrl = trailerUrl; @@ -88,7 +88,7 @@ namespace Kyoo.Models Poster = poster; Logo = logo; Backdrop = backdrop; - ExternalIDs = externalIDs; + ExternalIDs = externalIDs?.ToArray(); } public string GetID(string provider) diff --git a/Kyoo.Common/Models/Resources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs index 1bbb6014..07959b01 100644 --- a/Kyoo.Common/Models/Resources/Studio.cs +++ b/Kyoo.Common/Models/Resources/Studio.cs @@ -9,7 +9,7 @@ namespace Kyoo.Models public string Slug { get; set; } public string Name { get; set; } - [JsonIgnore] public virtual IEnumerable Shows { get; set; } + [JsonIgnore] public virtual ICollection Shows { get; set; } public Studio() { } diff --git a/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs b/Kyoo.CommonAPI/ALibraryManager.cs similarity index 97% rename from Kyoo.Common/Controllers/Implementations/ALibraryManager.cs rename to Kyoo.CommonAPI/ALibraryManager.cs index ab99f4f8..d809d334 100644 --- a/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs +++ b/Kyoo.CommonAPI/ALibraryManager.cs @@ -6,7 +6,7 @@ using Kyoo.Models; namespace Kyoo.Controllers { - public abstract class ALibraryManager : ILibraryManager + public class LibraryManager : ILibraryManager { public ILibraryRepository LibraryRepository { get; } public ILibraryItemRepository LibraryItemRepository { get; } @@ -19,8 +19,8 @@ namespace Kyoo.Controllers public IStudioRepository StudioRepository { get; } public IPeopleRepository PeopleRepository { get; } public IProviderRepository ProviderRepository { get; } - - public ALibraryManager(ILibraryRepository libraryRepository, + + protected LibraryManager(ILibraryRepository libraryRepository, ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, @@ -235,9 +235,12 @@ namespace Kyoo.Controllers return PeopleRepository.Get(where); } - public abstract Task Load(T obj, Expression> member) + public virtual Task Load(T obj, Expression> member) where T : class, IResource - where T2 : class; + where T2 : class + { + return Task.CompletedTask; + } public Task> GetLibraries(Expression> where = null, Sort sort = default, @@ -544,7 +547,7 @@ namespace Kyoo.Controllers return PeopleRepository.Edit(people, resetOld); } - public Task DelteLibrary(Library library) + public Task DeleteLibrary(Library library) { return LibraryRepository.Delete(library); } @@ -589,7 +592,7 @@ namespace Kyoo.Controllers return PeopleRepository.Delete(people); } - public Task DelteLibrary(string library) + public Task DeleteLibrary(string library) { return LibraryRepository.Delete(library); } @@ -634,7 +637,7 @@ namespace Kyoo.Controllers return PeopleRepository.Delete(people); } - public Task DelteLibrary(int library) + public Task DeleteLibrary(int library) { return LibraryRepository.Delete(library); } diff --git a/Kyoo/Controllers/LibraryManager.cs b/Kyoo.CommonAPI/LibraryManager.cs similarity index 88% rename from Kyoo/Controllers/LibraryManager.cs rename to Kyoo.CommonAPI/LibraryManager.cs index 593b0645..5df48c89 100644 --- a/Kyoo/Controllers/LibraryManager.cs +++ b/Kyoo.CommonAPI/LibraryManager.cs @@ -3,15 +3,16 @@ using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Kyoo.Controllers { - public class LibraryManager : ALibraryManager + public class TLibraryManager : LibraryManager { - private readonly DatabaseContext _database; + private readonly DbContext _database; - public LibraryManager(ILibraryRepository libraryRepository, + public TLibraryManager(ILibraryRepository libraryRepository, ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, @@ -22,7 +23,7 @@ namespace Kyoo.Controllers IStudioRepository studioRepository, IProviderRepository providerRepository, IPeopleRepository peopleRepository, - DatabaseContext database) + DbContext database) : base(libraryRepository, libraryItemRepository, collectionRepository, diff --git a/Kyoo/Controllers/ProviderManager.cs b/Kyoo/Controllers/ProviderManager.cs index 346f71be..1efea8e5 100644 --- a/Kyoo/Controllers/ProviderManager.cs +++ b/Kyoo/Controllers/ProviderManager.cs @@ -40,7 +40,7 @@ namespace Kyoo.Controllers } private async Task> GetMetadata( - Func>> providerCall, + Func>> providerCall, Library library, string what) { @@ -146,7 +146,7 @@ namespace Kyoo.Controllers return episode; } - public async Task> GetPeople(Show show, Library library) + public async Task> GetPeople(Show show, Library library) { List people = await GetMetadata( provider => provider.GetPeople(show), diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index cb1faf12..7c15982f 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -20,6 +20,8 @@ + + @@ -28,7 +30,6 @@ - @@ -45,7 +46,6 @@ - diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 96744687..236b82c2 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -44,7 +44,7 @@ namespace Kyoo NpgsqlConnection.GlobalTypeMapper.MapEnum(); } - private readonly ValueComparer> _stringArrayComparer = new( + private readonly ValueComparer> _stringArrayComparer = new( (l1, l2) => l1.SequenceEqual(l2), arr => arr.Aggregate(0, (i, s) => s.GetHashCode()) ); diff --git a/Kyoo/Models/Resources/CollectionDE.cs b/Kyoo/Models/Resources/CollectionDE.cs index ddc02b55..622e602e 100644 --- a/Kyoo/Models/Resources/CollectionDE.cs +++ b/Kyoo/Models/Resources/CollectionDE.cs @@ -8,18 +8,18 @@ namespace Kyoo.Models { [JsonIgnore] [NotMergable] public virtual ICollection Links { get; set; } [ExpressionRewrite(nameof(Links), nameof(CollectionLink.Child))] - public override IEnumerable Shows + public override ICollection Shows { - get => Links?.Select(x => x.Child); + get => Links?.Select(x => x.Child).ToList(); set => Links = value?.Select(x => new CollectionLink(this, x)).ToList(); } [JsonIgnore] [NotMergable] public virtual ICollection LibraryLinks { get; set; } [ExpressionRewrite(nameof(LibraryLinks), nameof(GenreLink.Child))] - public override IEnumerable Libraries + public override ICollection Libraries { - get => LibraryLinks?.Select(x => x.Library); + get => LibraryLinks?.Select(x => x.Library).ToList(); set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)).ToList(); } diff --git a/Kyoo/Models/Resources/GenreDE.cs b/Kyoo/Models/Resources/GenreDE.cs index 1832cd38..892be000 100644 --- a/Kyoo/Models/Resources/GenreDE.cs +++ b/Kyoo/Models/Resources/GenreDE.cs @@ -9,9 +9,9 @@ namespace Kyoo.Models [JsonIgnore] [NotMergable] public virtual ICollection Links { get; set; } [ExpressionRewrite(nameof(Links), nameof(GenreLink.Child))] - [JsonIgnore] [NotMergable] public override IEnumerable Shows + [JsonIgnore] [NotMergable] public override ICollection Shows { - get => Links?.Select(x => x.Parent); + get => Links?.Select(x => x.Parent).ToList(); set => Links = value?.Select(x => new GenreLink(x, this)).ToList(); } diff --git a/Kyoo/Models/Resources/LibraryDE.cs b/Kyoo/Models/Resources/LibraryDE.cs index 1bd097bf..32992c6c 100644 --- a/Kyoo/Models/Resources/LibraryDE.cs +++ b/Kyoo/Models/Resources/LibraryDE.cs @@ -8,25 +8,25 @@ namespace Kyoo.Models { [EditableRelation] [JsonIgnore] [NotMergable] public virtual ICollection ProviderLinks { get; set; } [ExpressionRewrite(nameof(ProviderLinks), nameof(ProviderLink.Child))] - public override IEnumerable Providers + public override ICollection Providers { - get => ProviderLinks?.Select(x => x.Child); + get => ProviderLinks?.Select(x => x.Child).ToList(); set => ProviderLinks = value?.Select(x => new ProviderLink(x, this)).ToList(); } [JsonIgnore] [NotMergable] public virtual ICollection Links { get; set; } [ExpressionRewrite(nameof(Links), nameof(LibraryLink.Show))] - public override IEnumerable Shows + public override ICollection Shows { - get => Links?.Where(x => x.Show != null).Select(x => x.Show); + get => Links?.Where(x => x.Show != null).Select(x => x.Show).ToList(); set => Links = Utility.MergeLists( value?.Select(x => new LibraryLink(this, x)), Links?.Where(x => x.Show == null))?.ToList(); } [ExpressionRewrite(nameof(Links), nameof(LibraryLink.Collection))] - public override IEnumerable Collections + public override ICollection Collections { - get => Links?.Where(x => x.Collection != null).Select(x => x.Collection); + get => Links?.Where(x => x.Collection != null).Select(x => x.Collection).ToList(); set => Links = Utility.MergeLists( value?.Select(x => new LibraryLink(this, x)), Links?.Where(x => x.Collection == null))?.ToList(); diff --git a/Kyoo/Models/Resources/ShowDE.cs b/Kyoo/Models/Resources/ShowDE.cs index 04e26994..e1928214 100644 --- a/Kyoo/Models/Resources/ShowDE.cs +++ b/Kyoo/Models/Resources/ShowDE.cs @@ -8,25 +8,25 @@ namespace Kyoo.Models { [EditableRelation] [JsonReadOnly] [NotMergable] public virtual ICollection GenreLinks { get; set; } [ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Child))] - public override IEnumerable Genres + public override ICollection Genres { - get => GenreLinks?.Select(x => x.Child); + get => GenreLinks?.Select(x => x.Child).ToList(); set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList(); } [JsonReadOnly] [NotMergable] public virtual ICollection LibraryLinks { get; set; } [ExpressionRewrite(nameof(LibraryLinks), nameof(LibraryLink.Library))] - public override IEnumerable Libraries + public override ICollection Libraries { - get => LibraryLinks?.Select(x => x.Library); + get => LibraryLinks?.Select(x => x.Library).ToList(); set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)).ToList(); } [JsonReadOnly] [NotMergable] public virtual ICollection CollectionLinks { get; set; } [ExpressionRewrite(nameof(CollectionLinks), nameof(CollectionLink.Parent))] - public override IEnumerable Collections + public override ICollection Collections { - get => CollectionLinks?.Select(x => x.Parent); + get => CollectionLinks?.Select(x => x.Parent).ToList(); set => CollectionLinks = value?.Select(x => new CollectionLink(x, this)).ToList(); } diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index bc6ef89b..eae316f2 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -29,7 +29,7 @@ namespace Kyoo _ => null }; - if (debug == null) + if (debug == null && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ENVIRONEMENT"))) Console.WriteLine($"Invalid ENVIRONEMENT variable. Supported values are \"debug\" and \"prod\". Ignoring..."); Console.WriteLine($"Running as {Environment.UserName}."); diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 9f32b56d..948bdb7a 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -147,7 +147,7 @@ namespace Kyoo services.AddScoped(); services.AddScoped(); - services.AddScoped(); + // services.AddScoped(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 35eb2ae1..92ba2342 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -75,9 +75,8 @@ namespace Kyoo.Controllers ICollection libraries = argument == null ? await libraryManager.GetLibraries() : new [] { await libraryManager.GetLibrary(argument)}; - // TODO replace this grotesque way to load the providers. foreach (Library library in libraries) - library.Providers = library.Providers; + await libraryManager.Load(library, x => x.Providers); foreach (Library library in libraries) await Scan(library, episodes, tracks, cancellationToken); @@ -353,7 +352,7 @@ namespace Kyoo.Controllers return episode; } - private async Task> GetTracks(Episode episode) + private async Task> GetTracks(Episode episode) { episode.Tracks = (await _transcoder.ExtractInfos(episode.Path)) .Where(x => x.Type != StreamType.Font) diff --git a/Kyoo/Tasks/ExtractMetadata.cs b/Kyoo/Tasks/ExtractMetadata.cs index ac613e0e..85c81612 100644 --- a/Kyoo/Tasks/ExtractMetadata.cs +++ b/Kyoo/Tasks/ExtractMetadata.cs @@ -99,8 +99,8 @@ namespace Kyoo.Tasks if (subs) { // TODO handle external subtites. - episode.Tracks = (await _transcoder!.ExtractInfos(episode.Path)) - .Where(x => x.Type != StreamType.Font); + episode.Tracks = (await _transcoder!.ExtractInfos(episode.Path)) + .Where(x => x.Type != StreamType.Font).ToArray(); await _library.EditEpisode(episode, false); } } From 5c848ca01c3104d34470e4a2f8bbadd20ca587d2 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Feb 2021 22:04:51 +0100 Subject: [PATCH 05/54] Fixing dotnet-ef types --- Kyoo.Common/Models/Resources/Library.cs | 2 +- Kyoo.Common/Models/Resources/Show.cs | 2 +- Kyoo/Kyoo.csproj | 1 + Kyoo/Models/DatabaseContext.cs | 14 +- ....cs => 20210216205007_Initial.Designer.cs} | 440 ++++++++++++------ ...2_Initial.cs => 20210216205007_Initial.cs} | 426 +++++++++-------- .../ConfigurationDbContextModelSnapshot.cs | 438 +++++++++++------ ....cs => 20210216205030_Initial.Designer.cs} | 117 +++-- ...4_Initial.cs => 20210216205030_Initial.cs} | 130 +++--- .../IdentityDatabaseModelSnapshot.cs | 115 +++-- ....cs => 20210216202218_Initial.Designer.cs} | 128 ++++- ...2_Initial.cs => 20210216202218_Initial.cs} | 216 ++++----- .../Internal/DatabaseContextModelSnapshot.cs | 126 ++++- Kyoo/Models/IdentityDatabase.cs | 1 + Kyoo/Startup.cs | 4 +- Kyoo/Tasks/CreateDatabase.cs | 6 +- 16 files changed, 1371 insertions(+), 795 deletions(-) rename Kyoo/Models/DatabaseMigrations/IdentityConfiguration/{20200526235342_Initial.Designer.cs => 20210216205007_Initial.Designer.cs} (79%) rename Kyoo/Models/DatabaseMigrations/IdentityConfiguration/{20200526235342_Initial.cs => 20210216205007_Initial.cs} (52%) rename Kyoo/Models/DatabaseMigrations/IdentityDatbase/{20200526235424_Initial.Designer.cs => 20210216205030_Initial.Designer.cs} (84%) rename Kyoo/Models/DatabaseMigrations/IdentityDatbase/{20200526235424_Initial.cs => 20210216205030_Initial.cs} (56%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210128212212_Initial.Designer.cs => 20210216202218_Initial.Designer.cs} (87%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210128212212_Initial.cs => 20210216202218_Initial.cs} (73%) diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index de3ea5fc..59e5b01d 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -9,7 +9,7 @@ namespace Kyoo.Models [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } - public ICollection Paths { get; set; } + public string[] Paths { get; set; } [EditableRelation] public virtual ICollection Providers { get; set; } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 675f45dc..fadac0bd 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -9,7 +9,7 @@ namespace Kyoo.Models public int ID { get; set; } public string Slug { get; set; } public string Title { get; set; } - [EditableRelation] public ICollection Aliases { get; set; } + [EditableRelation] public string[] Aliases { get; set; } [JsonIgnore] public string Path { get; set; } public string Overview { get; set; } public Status? Status { get; set; } diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 7c15982f..54911115 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -105,6 +105,7 @@ + diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 236b82c2..e7470268 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -43,11 +42,6 @@ namespace Kyoo NpgsqlConnection.GlobalTypeMapper.MapEnum(); NpgsqlConnection.GlobalTypeMapper.MapEnum(); } - - private readonly ValueComparer> _stringArrayComparer = new( - (l1, l2) => l1.SequenceEqual(l2), - arr => arr.Aggregate(0, (i, s) => s.GetHashCode()) - ); protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -61,16 +55,14 @@ namespace Kyoo modelBuilder.Ignore(); modelBuilder.Ignore(); modelBuilder.Ignore(); - + modelBuilder.Entity() .Property(x => x.Paths) - .HasColumnType("text[]") - .Metadata.SetValueComparer(_stringArrayComparer); + .HasColumnType("text[]"); modelBuilder.Entity() .Property(x => x.Aliases) - .HasColumnType("text[]") - .Metadata.SetValueComparer(_stringArrayComparer); + .HasColumnType("text[]"); modelBuilder.Entity() .Property(t => t.IsDefault) diff --git a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20200526235342_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.Designer.cs similarity index 79% rename from Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20200526235342_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.Designer.cs index 02ebc8c3..87d6032d 100644 --- a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20200526235342_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.Designer.cs @@ -10,16 +10,16 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration { [DbContext(typeof(ConfigurationDbContext))] - [Migration("20200526235342_Initial")] + [Migration("20210216205007_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => { @@ -28,16 +28,20 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property("AllowedAccessTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + b.Property("Created") .HasColumnType("timestamp without time zone"); b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); b.Property("DisplayName") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Enabled") .HasColumnType("boolean"); @@ -47,12 +51,15 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Name") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("NonEditable") .HasColumnType("boolean"); + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + b.Property("Updated") .HasColumnType("timestamp without time zone"); @@ -76,14 +83,14 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Type") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("Id"); b.HasIndex("ApiResourceId"); - b.ToTable("ApiClaims"); + b.ToTable("ApiResourceClaims"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => @@ -98,22 +105,22 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Key") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); b.HasIndex("ApiResourceId"); - b.ToTable("ApiProperties"); + b.ToTable("ApiResourceProperties"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -123,21 +130,80 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("ApiResourceId") .HasColumnType("integer"); + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Expiration") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); b.Property("DisplayName") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Emphasize") .HasColumnType("boolean"); + b.Property("Enabled") + .HasColumnType("boolean"); + b.Property("Name") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Required") .HasColumnType("boolean"); @@ -147,8 +213,6 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.HasKey("Id"); - b.HasIndex("ApiResourceId"); - b.HasIndex("Name") .IsUnique(); @@ -162,56 +226,46 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ApiScopeId") + b.Property("ScopeId") .HasColumnType("integer"); b.Property("Type") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("Id"); - b.HasIndex("ApiScopeId"); + b.HasIndex("ScopeId"); b.ToTable("ApiScopeClaims"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); - - b.Property("Expiration") - .HasColumnType("timestamp without time zone"); - - b.Property("Type") + b.Property("Key") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ScopeId") + .HasColumnType("integer"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(4000)") - .HasMaxLength(4000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); - b.HasIndex("ApiResourceId"); + b.HasIndex("ScopeId"); - b.ToTable("ApiSecrets"); + b.ToTable("ApiScopeProperties"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => @@ -242,6 +296,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("AllowRememberConsent") .HasColumnType("boolean"); + b.Property("AllowedIdentityTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + b.Property("AlwaysIncludeUserClaimsInIdToken") .HasColumnType("boolean"); @@ -255,25 +313,25 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("boolean"); b.Property("BackChannelLogoutUri") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("ClientClaimsPrefix") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientId") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientName") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientUri") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("ConsentLifetime") .HasColumnType("integer"); @@ -282,8 +340,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); b.Property("DeviceCodeLifetime") .HasColumnType("integer"); @@ -298,8 +356,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("boolean"); b.Property("FrontChannelLogoutUri") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("IdentityTokenLifetime") .HasColumnType("integer"); @@ -311,20 +369,20 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("LogoUri") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("NonEditable") .HasColumnType("boolean"); b.Property("PairWiseSubjectSalt") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ProtocolType") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("RefreshTokenExpiration") .HasColumnType("integer"); @@ -341,6 +399,9 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("RequirePkce") .HasColumnType("boolean"); + b.Property("RequireRequestObject") + .HasColumnType("boolean"); + b.Property("SlidingRefreshTokenLifetime") .HasColumnType("integer"); @@ -351,8 +412,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("UserCodeType") - .HasColumnType("character varying(100)") - .HasMaxLength(100); + .HasMaxLength(100) + .HasColumnType("character varying(100)"); b.Property("UserSsoLifetime") .HasColumnType("integer"); @@ -377,13 +438,13 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Type") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.HasKey("Id"); @@ -404,8 +465,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Origin") .IsRequired() - .HasColumnType("character varying(150)") - .HasMaxLength(150); + .HasMaxLength(150) + .HasColumnType("character varying(150)"); b.HasKey("Id"); @@ -426,8 +487,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("GrantType") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.HasKey("Id"); @@ -448,8 +509,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Provider") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("Id"); @@ -470,8 +531,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("PostLogoutRedirectUri") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); @@ -492,13 +553,13 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Key") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); @@ -519,8 +580,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("RedirectUri") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); @@ -541,8 +602,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Scope") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("Id"); @@ -565,21 +626,21 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("Description") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("Expiration") .HasColumnType("timestamp without time zone"); b.Property("Type") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(4000)") - .HasMaxLength(4000); + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); b.HasKey("Id"); @@ -588,28 +649,6 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.ToTable("ClientSecrets"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityClaims"); - }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => { b.Property("Id") @@ -621,12 +660,12 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); b.Property("DisplayName") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Emphasize") .HasColumnType("boolean"); @@ -636,8 +675,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Name") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("NonEditable") .HasColumnType("boolean"); @@ -659,6 +698,28 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.ToTable("IdentityResources"); }); + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("IdentityResourceId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceClaims"); + }); + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => { b.Property("Id") @@ -671,19 +732,19 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Key") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); b.HasIndex("IdentityResourceId"); - b.ToTable("IdentityProperties"); + b.ToTable("IdentityResourceProperties"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => @@ -693,6 +754,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ApiResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("ApiResource"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => @@ -702,33 +765,52 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ApiResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("ApiResource"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => { b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") .WithMany("Scopes") .HasForeignKey("ApiResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("ApiResource"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") - .WithMany("UserClaims") - .HasForeignKey("ApiScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => { b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") .WithMany("Secrets") .HasForeignKey("ApiResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("UserClaims") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("Properties") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => @@ -738,6 +820,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => @@ -747,6 +831,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => @@ -756,6 +842,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => @@ -765,6 +853,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => @@ -774,6 +864,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => @@ -783,6 +875,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => @@ -792,6 +886,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => @@ -801,6 +897,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => @@ -810,15 +908,19 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => { b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") .WithMany("UserClaims") .HasForeignKey("IdentityResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("IdentityResource"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => @@ -828,6 +930,54 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("IdentityResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Navigation("Properties"); + + b.Navigation("Scopes"); + + b.Navigation("Secrets"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Navigation("AllowedCorsOrigins"); + + b.Navigation("AllowedGrantTypes"); + + b.Navigation("AllowedScopes"); + + b.Navigation("Claims"); + + b.Navigation("ClientSecrets"); + + b.Navigation("IdentityProviderRestrictions"); + + b.Navigation("PostLogoutRedirectUris"); + + b.Navigation("Properties"); + + b.Navigation("RedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20200526235342_Initial.cs b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.cs similarity index 52% rename from Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20200526235342_Initial.cs rename to Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.cs index 447f9385..fec7b0f0 100644 --- a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20200526235342_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.cs @@ -12,69 +12,92 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ApiResources", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Enabled = table.Column(nullable: false), - Name = table.Column(maxLength: 200, nullable: false), - DisplayName = table.Column(maxLength: 200, nullable: true), - Description = table.Column(maxLength: 1000, nullable: true), - Created = table.Column(nullable: false), - Updated = table.Column(nullable: true), - LastAccessed = table.Column(nullable: true), - NonEditable = table.Column(nullable: false) + Enabled = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + AllowedAccessTokenSigningAlgorithms = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false), + Updated = table.Column(type: "timestamp without time zone", nullable: true), + LastAccessed = table.Column(type: "timestamp without time zone", nullable: true), + NonEditable = table.Column(type: "boolean", nullable: false) }, constraints: table => { table.PrimaryKey("PK_ApiResources", x => x.Id); }); + migrationBuilder.CreateTable( + name: "ApiScopes", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Enabled = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + Required = table.Column(type: "boolean", nullable: false), + Emphasize = table.Column(type: "boolean", nullable: false), + ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopes", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Clients", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Enabled = table.Column(nullable: false), - ClientId = table.Column(maxLength: 200, nullable: false), - ProtocolType = table.Column(maxLength: 200, nullable: false), - RequireClientSecret = table.Column(nullable: false), - ClientName = table.Column(maxLength: 200, nullable: true), - Description = table.Column(maxLength: 1000, nullable: true), - ClientUri = table.Column(maxLength: 2000, nullable: true), - LogoUri = table.Column(maxLength: 2000, nullable: true), - RequireConsent = table.Column(nullable: false), - AllowRememberConsent = table.Column(nullable: false), - AlwaysIncludeUserClaimsInIdToken = table.Column(nullable: false), - RequirePkce = table.Column(nullable: false), - AllowPlainTextPkce = table.Column(nullable: false), - AllowAccessTokensViaBrowser = table.Column(nullable: false), - FrontChannelLogoutUri = table.Column(maxLength: 2000, nullable: true), - FrontChannelLogoutSessionRequired = table.Column(nullable: false), - BackChannelLogoutUri = table.Column(maxLength: 2000, nullable: true), - BackChannelLogoutSessionRequired = table.Column(nullable: false), - AllowOfflineAccess = table.Column(nullable: false), - IdentityTokenLifetime = table.Column(nullable: false), - AccessTokenLifetime = table.Column(nullable: false), - AuthorizationCodeLifetime = table.Column(nullable: false), - ConsentLifetime = table.Column(nullable: true), - AbsoluteRefreshTokenLifetime = table.Column(nullable: false), - SlidingRefreshTokenLifetime = table.Column(nullable: false), - RefreshTokenUsage = table.Column(nullable: false), - UpdateAccessTokenClaimsOnRefresh = table.Column(nullable: false), - RefreshTokenExpiration = table.Column(nullable: false), - AccessTokenType = table.Column(nullable: false), - EnableLocalLogin = table.Column(nullable: false), - IncludeJwtId = table.Column(nullable: false), - AlwaysSendClientClaims = table.Column(nullable: false), - ClientClaimsPrefix = table.Column(maxLength: 200, nullable: true), - PairWiseSubjectSalt = table.Column(maxLength: 200, nullable: true), - Created = table.Column(nullable: false), - Updated = table.Column(nullable: true), - LastAccessed = table.Column(nullable: true), - UserSsoLifetime = table.Column(nullable: true), - UserCodeType = table.Column(maxLength: 100, nullable: true), - DeviceCodeLifetime = table.Column(nullable: false), - NonEditable = table.Column(nullable: false) + Enabled = table.Column(type: "boolean", nullable: false), + ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ProtocolType = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + RequireClientSecret = table.Column(type: "boolean", nullable: false), + ClientName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + ClientUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + LogoUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + RequireConsent = table.Column(type: "boolean", nullable: false), + AllowRememberConsent = table.Column(type: "boolean", nullable: false), + AlwaysIncludeUserClaimsInIdToken = table.Column(type: "boolean", nullable: false), + RequirePkce = table.Column(type: "boolean", nullable: false), + AllowPlainTextPkce = table.Column(type: "boolean", nullable: false), + RequireRequestObject = table.Column(type: "boolean", nullable: false), + AllowAccessTokensViaBrowser = table.Column(type: "boolean", nullable: false), + FrontChannelLogoutUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + FrontChannelLogoutSessionRequired = table.Column(type: "boolean", nullable: false), + BackChannelLogoutUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + BackChannelLogoutSessionRequired = table.Column(type: "boolean", nullable: false), + AllowOfflineAccess = table.Column(type: "boolean", nullable: false), + IdentityTokenLifetime = table.Column(type: "integer", nullable: false), + AllowedIdentityTokenSigningAlgorithms = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + AccessTokenLifetime = table.Column(type: "integer", nullable: false), + AuthorizationCodeLifetime = table.Column(type: "integer", nullable: false), + ConsentLifetime = table.Column(type: "integer", nullable: true), + AbsoluteRefreshTokenLifetime = table.Column(type: "integer", nullable: false), + SlidingRefreshTokenLifetime = table.Column(type: "integer", nullable: false), + RefreshTokenUsage = table.Column(type: "integer", nullable: false), + UpdateAccessTokenClaimsOnRefresh = table.Column(type: "boolean", nullable: false), + RefreshTokenExpiration = table.Column(type: "integer", nullable: false), + AccessTokenType = table.Column(type: "integer", nullable: false), + EnableLocalLogin = table.Column(type: "boolean", nullable: false), + IncludeJwtId = table.Column(type: "boolean", nullable: false), + AlwaysSendClientClaims = table.Column(type: "boolean", nullable: false), + ClientClaimsPrefix = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + PairWiseSubjectSalt = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Created = table.Column(type: "timestamp without time zone", nullable: false), + Updated = table.Column(type: "timestamp without time zone", nullable: true), + LastAccessed = table.Column(type: "timestamp without time zone", nullable: true), + UserSsoLifetime = table.Column(type: "integer", nullable: true), + UserCodeType = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + DeviceCodeLifetime = table.Column(type: "integer", nullable: false), + NonEditable = table.Column(type: "boolean", nullable: false) }, constraints: table => { @@ -85,18 +108,18 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "IdentityResources", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Enabled = table.Column(nullable: false), - Name = table.Column(maxLength: 200, nullable: false), - DisplayName = table.Column(maxLength: 200, nullable: true), - Description = table.Column(maxLength: 1000, nullable: true), - Required = table.Column(nullable: false), - Emphasize = table.Column(nullable: false), - ShowInDiscoveryDocument = table.Column(nullable: false), - Created = table.Column(nullable: false), - Updated = table.Column(nullable: true), - NonEditable = table.Column(nullable: false) + Enabled = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + Required = table.Column(type: "boolean", nullable: false), + Emphasize = table.Column(type: "boolean", nullable: false), + ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false), + Updated = table.Column(type: "timestamp without time zone", nullable: true), + NonEditable = table.Column(type: "boolean", nullable: false) }, constraints: table => { @@ -104,19 +127,19 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration }); migrationBuilder.CreateTable( - name: "ApiClaims", + name: "ApiResourceClaims", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Type = table.Column(maxLength: 200, nullable: false), - ApiResourceId = table.Column(nullable: false) + ApiResourceId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) }, constraints: table => { - table.PrimaryKey("PK_ApiClaims", x => x.Id); + table.PrimaryKey("PK_ApiResourceClaims", x => x.Id); table.ForeignKey( - name: "FK_ApiClaims_ApiResources_ApiResourceId", + name: "FK_ApiResourceClaims_ApiResources_ApiResourceId", column: x => x.ApiResourceId, principalTable: "ApiResources", principalColumn: "Id", @@ -124,20 +147,20 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration }); migrationBuilder.CreateTable( - name: "ApiProperties", + name: "ApiResourceProperties", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Key = table.Column(maxLength: 250, nullable: false), - Value = table.Column(maxLength: 2000, nullable: false), - ApiResourceId = table.Column(nullable: false) + ApiResourceId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) }, constraints: table => { - table.PrimaryKey("PK_ApiProperties", x => x.Id); + table.PrimaryKey("PK_ApiResourceProperties", x => x.Id); table.ForeignKey( - name: "FK_ApiProperties_ApiResources_ApiResourceId", + name: "FK_ApiResourceProperties_ApiResources_ApiResourceId", column: x => x.ApiResourceId, principalTable: "ApiResources", principalColumn: "Id", @@ -145,24 +168,19 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration }); migrationBuilder.CreateTable( - name: "ApiScopes", + name: "ApiResourceScopes", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Name = table.Column(maxLength: 200, nullable: false), - DisplayName = table.Column(maxLength: 200, nullable: true), - Description = table.Column(maxLength: 1000, nullable: true), - Required = table.Column(nullable: false), - Emphasize = table.Column(nullable: false), - ShowInDiscoveryDocument = table.Column(nullable: false), - ApiResourceId = table.Column(nullable: false) + Scope = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ApiResourceId = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_ApiScopes", x => x.Id); + table.PrimaryKey("PK_ApiResourceScopes", x => x.Id); table.ForeignKey( - name: "FK_ApiScopes_ApiResources_ApiResourceId", + name: "FK_ApiResourceScopes_ApiResources_ApiResourceId", column: x => x.ApiResourceId, principalTable: "ApiResources", principalColumn: "Id", @@ -170,38 +188,79 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration }); migrationBuilder.CreateTable( - name: "ApiSecrets", + name: "ApiResourceSecrets", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Description = table.Column(maxLength: 1000, nullable: true), - Value = table.Column(maxLength: 4000, nullable: false), - Expiration = table.Column(nullable: true), - Type = table.Column(maxLength: 250, nullable: false), - Created = table.Column(nullable: false), - ApiResourceId = table.Column(nullable: false) + ApiResourceId = table.Column(type: "integer", nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + Value = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: false), + Expiration = table.Column(type: "timestamp without time zone", nullable: true), + Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_ApiSecrets", x => x.Id); + table.PrimaryKey("PK_ApiResourceSecrets", x => x.Id); table.ForeignKey( - name: "FK_ApiSecrets_ApiResources_ApiResourceId", + name: "FK_ApiResourceSecrets_ApiResources_ApiResourceId", column: x => x.ApiResourceId, principalTable: "ApiResources", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "ApiScopeClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ScopeId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeClaims_ApiScopes_ScopeId", + column: x => x.ScopeId, + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopeProperties", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ScopeId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeProperties", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeProperties_ApiScopes_ScopeId", + column: x => x.ScopeId, + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "ClientClaims", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Type = table.Column(maxLength: 250, nullable: false), - Value = table.Column(maxLength: 250, nullable: false), - ClientId = table.Column(nullable: false) + Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -218,10 +277,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ClientCorsOrigins", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Origin = table.Column(maxLength: 150, nullable: false), - ClientId = table.Column(nullable: false) + Origin = table.Column(type: "character varying(150)", maxLength: 150, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -238,10 +297,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ClientGrantTypes", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - GrantType = table.Column(maxLength: 250, nullable: false), - ClientId = table.Column(nullable: false) + GrantType = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -258,10 +317,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ClientIdPRestrictions", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Provider = table.Column(maxLength: 200, nullable: false), - ClientId = table.Column(nullable: false) + Provider = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -278,10 +337,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ClientPostLogoutRedirectUris", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - PostLogoutRedirectUri = table.Column(maxLength: 2000, nullable: false), - ClientId = table.Column(nullable: false) + PostLogoutRedirectUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -298,11 +357,11 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ClientProperties", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Key = table.Column(maxLength: 250, nullable: false), - Value = table.Column(maxLength: 2000, nullable: false), - ClientId = table.Column(nullable: false) + ClientId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) }, constraints: table => { @@ -319,10 +378,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ClientRedirectUris", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RedirectUri = table.Column(maxLength: 2000, nullable: false), - ClientId = table.Column(nullable: false) + RedirectUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -339,10 +398,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ClientScopes", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Scope = table.Column(maxLength: 200, nullable: false), - ClientId = table.Column(nullable: false) + Scope = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -359,14 +418,14 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ClientSecrets", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Description = table.Column(maxLength: 2000, nullable: true), - Value = table.Column(maxLength: 4000, nullable: false), - Expiration = table.Column(nullable: true), - Type = table.Column(maxLength: 250, nullable: false), - Created = table.Column(nullable: false), - ClientId = table.Column(nullable: false) + ClientId = table.Column(type: "integer", nullable: false), + Description = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + Value = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: false), + Expiration = table.Column(type: "timestamp without time zone", nullable: true), + Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false) }, constraints: table => { @@ -380,19 +439,19 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration }); migrationBuilder.CreateTable( - name: "IdentityClaims", + name: "IdentityResourceClaims", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Type = table.Column(maxLength: 200, nullable: false), - IdentityResourceId = table.Column(nullable: false) + IdentityResourceId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) }, constraints: table => { - table.PrimaryKey("PK_IdentityClaims", x => x.Id); + table.PrimaryKey("PK_IdentityResourceClaims", x => x.Id); table.ForeignKey( - name: "FK_IdentityClaims_IdentityResources_IdentityResourceId", + name: "FK_IdentityResourceClaims_IdentityResources_IdentityResourceId", column: x => x.IdentityResourceId, principalTable: "IdentityResources", principalColumn: "Id", @@ -400,54 +459,34 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration }); migrationBuilder.CreateTable( - name: "IdentityProperties", + name: "IdentityResourceProperties", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Key = table.Column(maxLength: 250, nullable: false), - Value = table.Column(maxLength: 2000, nullable: false), - IdentityResourceId = table.Column(nullable: false) + IdentityResourceId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) }, constraints: table => { - table.PrimaryKey("PK_IdentityProperties", x => x.Id); + table.PrimaryKey("PK_IdentityResourceProperties", x => x.Id); table.ForeignKey( - name: "FK_IdentityProperties_IdentityResources_IdentityResourceId", + name: "FK_IdentityResourceProperties_IdentityResources_IdentityResour~", column: x => x.IdentityResourceId, principalTable: "IdentityResources", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "ApiScopeClaims", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Type = table.Column(maxLength: 200, nullable: false), - ApiScopeId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); - table.ForeignKey( - name: "FK_ApiScopeClaims_ApiScopes_ApiScopeId", - column: x => x.ApiScopeId, - principalTable: "ApiScopes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateIndex( - name: "IX_ApiClaims_ApiResourceId", - table: "ApiClaims", + name: "IX_ApiResourceClaims_ApiResourceId", + table: "ApiResourceClaims", column: "ApiResourceId"); migrationBuilder.CreateIndex( - name: "IX_ApiProperties_ApiResourceId", - table: "ApiProperties", + name: "IX_ApiResourceProperties_ApiResourceId", + table: "ApiResourceProperties", column: "ApiResourceId"); migrationBuilder.CreateIndex( @@ -457,26 +496,31 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration unique: true); migrationBuilder.CreateIndex( - name: "IX_ApiScopeClaims_ApiScopeId", - table: "ApiScopeClaims", - column: "ApiScopeId"); + name: "IX_ApiResourceScopes_ApiResourceId", + table: "ApiResourceScopes", + column: "ApiResourceId"); migrationBuilder.CreateIndex( - name: "IX_ApiScopes_ApiResourceId", - table: "ApiScopes", + name: "IX_ApiResourceSecrets_ApiResourceId", + table: "ApiResourceSecrets", column: "ApiResourceId"); + migrationBuilder.CreateIndex( + name: "IX_ApiScopeClaims_ScopeId", + table: "ApiScopeClaims", + column: "ScopeId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopeProperties_ScopeId", + table: "ApiScopeProperties", + column: "ScopeId"); + migrationBuilder.CreateIndex( name: "IX_ApiScopes_Name", table: "ApiScopes", column: "Name", unique: true); - migrationBuilder.CreateIndex( - name: "IX_ApiSecrets_ApiResourceId", - table: "ApiSecrets", - column: "ApiResourceId"); - migrationBuilder.CreateIndex( name: "IX_ClientClaims_ClientId", table: "ClientClaims", @@ -529,13 +573,13 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration column: "ClientId"); migrationBuilder.CreateIndex( - name: "IX_IdentityClaims_IdentityResourceId", - table: "IdentityClaims", + name: "IX_IdentityResourceClaims_IdentityResourceId", + table: "IdentityResourceClaims", column: "IdentityResourceId"); migrationBuilder.CreateIndex( - name: "IX_IdentityProperties_IdentityResourceId", - table: "IdentityProperties", + name: "IX_IdentityResourceProperties_IdentityResourceId", + table: "IdentityResourceProperties", column: "IdentityResourceId"); migrationBuilder.CreateIndex( @@ -548,16 +592,22 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "ApiClaims"); + name: "ApiResourceClaims"); migrationBuilder.DropTable( - name: "ApiProperties"); + name: "ApiResourceProperties"); + + migrationBuilder.DropTable( + name: "ApiResourceScopes"); + + migrationBuilder.DropTable( + name: "ApiResourceSecrets"); migrationBuilder.DropTable( name: "ApiScopeClaims"); migrationBuilder.DropTable( - name: "ApiSecrets"); + name: "ApiScopeProperties"); migrationBuilder.DropTable( name: "ClientClaims"); @@ -587,10 +637,13 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration name: "ClientSecrets"); migrationBuilder.DropTable( - name: "IdentityClaims"); + name: "IdentityResourceClaims"); migrationBuilder.DropTable( - name: "IdentityProperties"); + name: "IdentityResourceProperties"); + + migrationBuilder.DropTable( + name: "ApiResources"); migrationBuilder.DropTable( name: "ApiScopes"); @@ -600,9 +653,6 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration migrationBuilder.DropTable( name: "IdentityResources"); - - migrationBuilder.DropTable( - name: "ApiResources"); } } } diff --git a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs index f83d80e5..c11ac33a 100644 --- a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs @@ -15,9 +15,9 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => { @@ -26,16 +26,20 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + b.Property("AllowedAccessTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + b.Property("Created") .HasColumnType("timestamp without time zone"); b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); b.Property("DisplayName") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Enabled") .HasColumnType("boolean"); @@ -45,12 +49,15 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Name") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("NonEditable") .HasColumnType("boolean"); + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + b.Property("Updated") .HasColumnType("timestamp without time zone"); @@ -74,14 +81,14 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Type") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("Id"); b.HasIndex("ApiResourceId"); - b.ToTable("ApiClaims"); + b.ToTable("ApiResourceClaims"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => @@ -96,22 +103,22 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Key") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); b.HasIndex("ApiResourceId"); - b.ToTable("ApiProperties"); + b.ToTable("ApiResourceProperties"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -121,21 +128,80 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("ApiResourceId") .HasColumnType("integer"); + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Expiration") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); b.Property("DisplayName") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Emphasize") .HasColumnType("boolean"); + b.Property("Enabled") + .HasColumnType("boolean"); + b.Property("Name") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Required") .HasColumnType("boolean"); @@ -145,8 +211,6 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.HasKey("Id"); - b.HasIndex("ApiResourceId"); - b.HasIndex("Name") .IsUnique(); @@ -160,56 +224,46 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ApiScopeId") + b.Property("ScopeId") .HasColumnType("integer"); b.Property("Type") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("Id"); - b.HasIndex("ApiScopeId"); + b.HasIndex("ScopeId"); b.ToTable("ApiScopeClaims"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); - - b.Property("Expiration") - .HasColumnType("timestamp without time zone"); - - b.Property("Type") + b.Property("Key") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ScopeId") + .HasColumnType("integer"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(4000)") - .HasMaxLength(4000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); - b.HasIndex("ApiResourceId"); + b.HasIndex("ScopeId"); - b.ToTable("ApiSecrets"); + b.ToTable("ApiScopeProperties"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => @@ -240,6 +294,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("AllowRememberConsent") .HasColumnType("boolean"); + b.Property("AllowedIdentityTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + b.Property("AlwaysIncludeUserClaimsInIdToken") .HasColumnType("boolean"); @@ -253,25 +311,25 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("boolean"); b.Property("BackChannelLogoutUri") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("ClientClaimsPrefix") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientId") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientName") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientUri") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("ConsentLifetime") .HasColumnType("integer"); @@ -280,8 +338,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); b.Property("DeviceCodeLifetime") .HasColumnType("integer"); @@ -296,8 +354,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("boolean"); b.Property("FrontChannelLogoutUri") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("IdentityTokenLifetime") .HasColumnType("integer"); @@ -309,20 +367,20 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("LogoUri") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("NonEditable") .HasColumnType("boolean"); b.Property("PairWiseSubjectSalt") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ProtocolType") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("RefreshTokenExpiration") .HasColumnType("integer"); @@ -339,6 +397,9 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("RequirePkce") .HasColumnType("boolean"); + b.Property("RequireRequestObject") + .HasColumnType("boolean"); + b.Property("SlidingRefreshTokenLifetime") .HasColumnType("integer"); @@ -349,8 +410,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("UserCodeType") - .HasColumnType("character varying(100)") - .HasMaxLength(100); + .HasMaxLength(100) + .HasColumnType("character varying(100)"); b.Property("UserSsoLifetime") .HasColumnType("integer"); @@ -375,13 +436,13 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Type") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.HasKey("Id"); @@ -402,8 +463,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Origin") .IsRequired() - .HasColumnType("character varying(150)") - .HasMaxLength(150); + .HasMaxLength(150) + .HasColumnType("character varying(150)"); b.HasKey("Id"); @@ -424,8 +485,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("GrantType") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.HasKey("Id"); @@ -446,8 +507,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Provider") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("Id"); @@ -468,8 +529,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("PostLogoutRedirectUri") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); @@ -490,13 +551,13 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Key") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); @@ -517,8 +578,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("RedirectUri") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); @@ -539,8 +600,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Scope") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("Id"); @@ -563,21 +624,21 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("Description") - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.Property("Expiration") .HasColumnType("timestamp without time zone"); b.Property("Type") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(4000)") - .HasMaxLength(4000); + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); b.HasKey("Id"); @@ -586,28 +647,6 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.ToTable("ClientSecrets"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityClaims"); - }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => { b.Property("Id") @@ -619,12 +658,12 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasColumnType("timestamp without time zone"); b.Property("Description") - .HasColumnType("character varying(1000)") - .HasMaxLength(1000); + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); b.Property("DisplayName") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Emphasize") .HasColumnType("boolean"); @@ -634,8 +673,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Name") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("NonEditable") .HasColumnType("boolean"); @@ -657,6 +696,28 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.ToTable("IdentityResources"); }); + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("IdentityResourceId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceClaims"); + }); + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => { b.Property("Id") @@ -669,19 +730,19 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration b.Property("Key") .IsRequired() - .HasColumnType("character varying(250)") - .HasMaxLength(250); + .HasMaxLength(250) + .HasColumnType("character varying(250)"); b.Property("Value") .IsRequired() - .HasColumnType("character varying(2000)") - .HasMaxLength(2000); + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); b.HasKey("Id"); b.HasIndex("IdentityResourceId"); - b.ToTable("IdentityProperties"); + b.ToTable("IdentityResourceProperties"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => @@ -691,6 +752,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ApiResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("ApiResource"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => @@ -700,33 +763,52 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ApiResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("ApiResource"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => { b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") .WithMany("Scopes") .HasForeignKey("ApiResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("ApiResource"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") - .WithMany("UserClaims") - .HasForeignKey("ApiScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => { b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") .WithMany("Secrets") .HasForeignKey("ApiResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("UserClaims") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("Properties") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => @@ -736,6 +818,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => @@ -745,6 +829,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => @@ -754,6 +840,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => @@ -763,6 +851,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => @@ -772,6 +862,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => @@ -781,6 +873,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => @@ -790,6 +884,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => @@ -799,6 +895,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => @@ -808,15 +906,19 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Client"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => { b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") .WithMany("UserClaims") .HasForeignKey("IdentityResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("IdentityResource"); }); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => @@ -826,6 +928,54 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration .HasForeignKey("IdentityResourceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Navigation("Properties"); + + b.Navigation("Scopes"); + + b.Navigation("Secrets"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Navigation("AllowedCorsOrigins"); + + b.Navigation("AllowedGrantTypes"); + + b.Navigation("AllowedScopes"); + + b.Navigation("Claims"); + + b.Navigation("ClientSecrets"); + + b.Navigation("IdentityProviderRestrictions"); + + b.Navigation("PostLogoutRedirectUris"); + + b.Navigation("Properties"); + + b.Navigation("RedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo/Models/DatabaseMigrations/IdentityDatbase/20200526235424_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/IdentityDatbase/20210216205030_Initial.Designer.cs similarity index 84% rename from Kyoo/Models/DatabaseMigrations/IdentityDatbase/20200526235424_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/IdentityDatbase/20210216205030_Initial.Designer.cs index ced0460b..10626a22 100644 --- a/Kyoo/Models/DatabaseMigrations/IdentityDatbase/20200526235424_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/IdentityDatbase/20210216205030_Initial.Designer.cs @@ -7,51 +7,59 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase +namespace Kyoo.Kyoo.Models.DatabaseMigrations.IdentityDatbase { [DbContext(typeof(IdentityDatabase))] - [Migration("20200526235424_Initial")] + [Migration("20210216205030_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => { b.Property("UserCode") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientId") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("CreationTime") .HasColumnType("timestamp without time zone"); b.Property("Data") .IsRequired() - .HasColumnType("character varying(50000)") - .HasMaxLength(50000); + .HasMaxLength(50000) + .HasColumnType("character varying(50000)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("DeviceCode") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Expiration") .IsRequired() .HasColumnType("timestamp without time zone"); + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + b.Property("SubjectId") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("UserCode"); @@ -66,33 +74,44 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => { b.Property("Key") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientId") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedTime") + .HasColumnType("timestamp without time zone"); b.Property("CreationTime") .HasColumnType("timestamp without time zone"); b.Property("Data") .IsRequired() - .HasColumnType("character varying(50000)") - .HasMaxLength(50000); + .HasMaxLength(50000) + .HasColumnType("character varying(50000)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Expiration") .HasColumnType("timestamp without time zone"); + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + b.Property("SubjectId") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Type") .IsRequired() - .HasColumnType("character varying(50)") - .HasMaxLength(50); + .HasMaxLength(50) + .HasColumnType("character varying(50)"); b.HasKey("Key"); @@ -100,6 +119,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase b.HasIndex("SubjectId", "ClientId", "Type"); + b.HasIndex("SubjectId", "SessionId", "Type"); + b.ToTable("PersistedGrants"); }); @@ -116,8 +137,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("text"); b.Property("Email") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("EmailConfirmed") .HasColumnType("boolean"); @@ -129,12 +150,12 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("timestamp with time zone"); b.Property("NormalizedEmail") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("NormalizedUserName") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("OTAC") .HasColumnType("text"); @@ -158,17 +179,17 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("boolean"); b.Property("UserName") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.HasKey("Id"); b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); + .HasDatabaseName("EmailIndex"); b.HasIndex("NormalizedUserName") .IsUnique() - .HasName("UserNameIndex"); + .HasDatabaseName("UserNameIndex"); b.ToTable("User"); }); @@ -183,18 +204,18 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("text"); b.Property("Name") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("NormalizedName") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.HasKey("Id"); b.HasIndex("NormalizedName") .IsUnique() - .HasName("RoleNameIndex"); + .HasDatabaseName("RoleNameIndex"); b.ToTable("UserRoles"); }); @@ -250,12 +271,12 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { b.Property("LoginProvider") - .HasColumnType("character varying(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("character varying(128)"); b.Property("ProviderKey") - .HasColumnType("character varying(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("character varying(128)"); b.Property("ProviderDisplayName") .HasColumnType("text"); @@ -292,12 +313,12 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("text"); b.Property("LoginProvider") - .HasColumnType("character varying(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("character varying(128)"); b.Property("Name") - .HasColumnType("character varying(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("character varying(128)"); b.Property("Value") .HasColumnType("text"); diff --git a/Kyoo/Models/DatabaseMigrations/IdentityDatbase/20200526235424_Initial.cs b/Kyoo/Models/DatabaseMigrations/IdentityDatbase/20210216205030_Initial.cs similarity index 56% rename from Kyoo/Models/DatabaseMigrations/IdentityDatbase/20200526235424_Initial.cs rename to Kyoo/Models/DatabaseMigrations/IdentityDatbase/20210216205030_Initial.cs index 15abc8d3..e0cffa4f 100644 --- a/Kyoo/Models/DatabaseMigrations/IdentityDatbase/20200526235424_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/IdentityDatbase/20210216205030_Initial.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase +namespace Kyoo.Kyoo.Models.DatabaseMigrations.IdentityDatbase { public partial class Initial : Migration { @@ -12,13 +12,15 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase name: "DeviceCodes", columns: table => new { - UserCode = table.Column(maxLength: 200, nullable: false), - DeviceCode = table.Column(maxLength: 200, nullable: false), - SubjectId = table.Column(maxLength: 200, nullable: true), - ClientId = table.Column(maxLength: 200, nullable: false), - CreationTime = table.Column(nullable: false), - Expiration = table.Column(nullable: false), - Data = table.Column(maxLength: 50000, nullable: false) + UserCode = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + DeviceCode = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + SubjectId = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + SessionId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + CreationTime = table.Column(type: "timestamp without time zone", nullable: false), + Expiration = table.Column(type: "timestamp without time zone", nullable: false), + Data = table.Column(type: "character varying(50000)", maxLength: 50000, nullable: false) }, constraints: table => { @@ -29,13 +31,16 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase name: "PersistedGrants", columns: table => new { - Key = table.Column(maxLength: 200, nullable: false), - Type = table.Column(maxLength: 50, nullable: false), - SubjectId = table.Column(maxLength: 200, nullable: true), - ClientId = table.Column(maxLength: 200, nullable: false), - CreationTime = table.Column(nullable: false), - Expiration = table.Column(nullable: true), - Data = table.Column(maxLength: 50000, nullable: false) + Key = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Type = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + SubjectId = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + SessionId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + CreationTime = table.Column(type: "timestamp without time zone", nullable: false), + Expiration = table.Column(type: "timestamp without time zone", nullable: true), + ConsumedTime = table.Column(type: "timestamp without time zone", nullable: true), + Data = table.Column(type: "character varying(50000)", maxLength: 50000, nullable: false) }, constraints: table => { @@ -46,23 +51,23 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase name: "User", columns: table => new { - Id = table.Column(nullable: false), - UserName = table.Column(maxLength: 256, nullable: true), - NormalizedUserName = table.Column(maxLength: 256, nullable: true), - Email = table.Column(maxLength: 256, nullable: true), - NormalizedEmail = table.Column(maxLength: 256, nullable: true), - EmailConfirmed = table.Column(nullable: false), - PasswordHash = table.Column(nullable: true), - SecurityStamp = table.Column(nullable: true), - ConcurrencyStamp = table.Column(nullable: true), - PhoneNumber = table.Column(nullable: true), - PhoneNumberConfirmed = table.Column(nullable: false), - TwoFactorEnabled = table.Column(nullable: false), - LockoutEnd = table.Column(nullable: true), - LockoutEnabled = table.Column(nullable: false), - AccessFailedCount = table.Column(nullable: false), - OTAC = table.Column(nullable: true), - OTACExpires = table.Column(nullable: true) + Id = table.Column(type: "text", nullable: false), + OTAC = table.Column(type: "text", nullable: true), + OTACExpires = table.Column(type: "timestamp without time zone", nullable: true), + UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "boolean", nullable: false), + PasswordHash = table.Column(type: "text", nullable: true), + SecurityStamp = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true), + PhoneNumber = table.Column(type: "text", nullable: true), + PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), + TwoFactorEnabled = table.Column(type: "boolean", nullable: false), + LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), + LockoutEnabled = table.Column(type: "boolean", nullable: false), + AccessFailedCount = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -73,10 +78,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase name: "UserRoles", columns: table => new { - Id = table.Column(nullable: false), - Name = table.Column(maxLength: 256, nullable: true), - NormalizedName = table.Column(maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(nullable: true) + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -87,11 +92,11 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase name: "UserClaim", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - UserId = table.Column(nullable: false), - ClaimType = table.Column(nullable: true), - ClaimValue = table.Column(nullable: true) + UserId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -108,10 +113,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase name: "UserLogin", columns: table => new { - LoginProvider = table.Column(maxLength: 128, nullable: false), - ProviderKey = table.Column(maxLength: 128, nullable: false), - ProviderDisplayName = table.Column(nullable: true), - UserId = table.Column(nullable: false) + LoginProvider = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + ProviderKey = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + ProviderDisplayName = table.Column(type: "text", nullable: true), + UserId = table.Column(type: "text", nullable: false) }, constraints: table => { @@ -128,10 +133,10 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase name: "UserToken", columns: table => new { - UserId = table.Column(nullable: false), - LoginProvider = table.Column(maxLength: 128, nullable: false), - Name = table.Column(maxLength: 128, nullable: false), - Value = table.Column(nullable: true) + UserId = table.Column(type: "text", nullable: false), + LoginProvider = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Value = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -148,35 +153,35 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase name: "UserRole", columns: table => new { - UserId = table.Column(nullable: false), - RoleId = table.Column(nullable: false) + UserId = table.Column(type: "text", nullable: false), + RoleId = table.Column(type: "text", nullable: false) }, constraints: table => { table.PrimaryKey("PK_UserRole", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_UserRole_UserRoles_RoleId", - column: x => x.RoleId, - principalTable: "UserRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); table.ForeignKey( name: "FK_UserRole_User_UserId", column: x => x.UserId, principalTable: "User", principalColumn: "Id", onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserRole_UserRoles_RoleId", + column: x => x.RoleId, + principalTable: "UserRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( name: "UserRoleClaim", columns: table => new { - Id = table.Column(nullable: false) + Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RoleId = table.Column(nullable: false), - ClaimType = table.Column(nullable: true), - ClaimValue = table.Column(nullable: true) + RoleId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -210,6 +215,11 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase table: "PersistedGrants", columns: new[] { "SubjectId", "ClientId", "Type" }); + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_SessionId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "SessionId", "Type" }); + migrationBuilder.CreateIndex( name: "EmailIndex", table: "User", diff --git a/Kyoo/Models/DatabaseMigrations/IdentityDatbase/IdentityDatabaseModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/IdentityDatbase/IdentityDatabaseModelSnapshot.cs index 995c15db..fa2da994 100644 --- a/Kyoo/Models/DatabaseMigrations/IdentityDatbase/IdentityDatabaseModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/IdentityDatbase/IdentityDatabaseModelSnapshot.cs @@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase +namespace Kyoo.Kyoo.Models.DatabaseMigrations.IdentityDatbase { [DbContext(typeof(IdentityDatabase))] partial class IdentityDatabaseModelSnapshot : ModelSnapshot @@ -15,41 +15,49 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => { b.Property("UserCode") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientId") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("CreationTime") .HasColumnType("timestamp without time zone"); b.Property("Data") .IsRequired() - .HasColumnType("character varying(50000)") - .HasMaxLength(50000); + .HasMaxLength(50000) + .HasColumnType("character varying(50000)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("DeviceCode") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Expiration") .IsRequired() .HasColumnType("timestamp without time zone"); + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + b.Property("SubjectId") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.HasKey("UserCode"); @@ -64,33 +72,44 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => { b.Property("Key") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("ClientId") .IsRequired() - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedTime") + .HasColumnType("timestamp without time zone"); b.Property("CreationTime") .HasColumnType("timestamp without time zone"); b.Property("Data") .IsRequired() - .HasColumnType("character varying(50000)") - .HasMaxLength(50000); + .HasMaxLength(50000) + .HasColumnType("character varying(50000)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Expiration") .HasColumnType("timestamp without time zone"); + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + b.Property("SubjectId") - .HasColumnType("character varying(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("character varying(200)"); b.Property("Type") .IsRequired() - .HasColumnType("character varying(50)") - .HasMaxLength(50); + .HasMaxLength(50) + .HasColumnType("character varying(50)"); b.HasKey("Key"); @@ -98,6 +117,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase b.HasIndex("SubjectId", "ClientId", "Type"); + b.HasIndex("SubjectId", "SessionId", "Type"); + b.ToTable("PersistedGrants"); }); @@ -114,8 +135,8 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("text"); b.Property("Email") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("EmailConfirmed") .HasColumnType("boolean"); @@ -127,12 +148,12 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("timestamp with time zone"); b.Property("NormalizedEmail") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("NormalizedUserName") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("OTAC") .HasColumnType("text"); @@ -156,17 +177,17 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("boolean"); b.Property("UserName") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.HasKey("Id"); b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); + .HasDatabaseName("EmailIndex"); b.HasIndex("NormalizedUserName") .IsUnique() - .HasName("UserNameIndex"); + .HasDatabaseName("UserNameIndex"); b.ToTable("User"); }); @@ -181,18 +202,18 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("text"); b.Property("Name") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.Property("NormalizedName") - .HasColumnType("character varying(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("character varying(256)"); b.HasKey("Id"); b.HasIndex("NormalizedName") .IsUnique() - .HasName("RoleNameIndex"); + .HasDatabaseName("RoleNameIndex"); b.ToTable("UserRoles"); }); @@ -248,12 +269,12 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { b.Property("LoginProvider") - .HasColumnType("character varying(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("character varying(128)"); b.Property("ProviderKey") - .HasColumnType("character varying(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("character varying(128)"); b.Property("ProviderDisplayName") .HasColumnType("text"); @@ -290,12 +311,12 @@ namespace Kyoo.Models.DatabaseMigrations.IdentityDatbase .HasColumnType("text"); b.Property("LoginProvider") - .HasColumnType("character varying(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("character varying(128)"); b.Property("Name") - .HasColumnType("character varying(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("character varying(128)"); b.Property("Value") .HasColumnType("text"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210128212212_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.Designer.cs similarity index 87% rename from Kyoo/Models/DatabaseMigrations/Internal/20210128212212_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.Designer.cs index e9e02cef..ece1dce0 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210128212212_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.Designer.cs @@ -1,6 +1,5 @@ // using System; -using System.Collections.Generic; using Kyoo; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -8,22 +7,22 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -namespace Kyoo.Models.DatabaseMigrations.Internal +namespace Kyoo.Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210128212212_Initial")] + [Migration("20210216202218_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Npgsql:Enum:item_type", "show,movie,collection") - .HasAnnotation("Npgsql:Enum:status", "finished,airing,planned,unknown") - .HasAnnotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,font") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) + .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) + .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); modelBuilder.Entity("Kyoo.Models.CollectionDE", b => { @@ -51,8 +50,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .IsUnique(); b.ToTable("Collections"); - - b.HasDiscriminator(); }); modelBuilder.Entity("Kyoo.Models.CollectionLink", b => @@ -140,8 +137,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .IsUnique(); b.ToTable("Genres"); - - b.HasDiscriminator(); }); modelBuilder.Entity("Kyoo.Models.GenreLink", b => @@ -169,7 +164,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Name") .HasColumnType("text"); - b.Property>("Paths") + b.Property("Paths") .HasColumnType("text[]"); b.Property("Slug") @@ -182,8 +177,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .IsUnique(); b.ToTable("Libraries"); - - b.HasDiscriminator(); }); modelBuilder.Entity("Kyoo.Models.LibraryLink", b => @@ -393,7 +386,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property>("Aliases") + b.Property("Aliases") .HasColumnType("text[]"); b.Property("Backdrop") @@ -444,8 +437,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("StudioID"); b.ToTable("Shows"); - - b.HasDiscriminator(); }); modelBuilder.Entity("Kyoo.Models.Studio", b => @@ -524,6 +515,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ParentID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Child"); + + b.Navigation("Parent"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -537,6 +532,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Season"); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.GenreLink", b => @@ -552,6 +551,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ParentID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Child"); + + b.Navigation("Parent"); }); modelBuilder.Entity("Kyoo.Models.LibraryLink", b => @@ -571,6 +574,12 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .WithMany("LibraryLinks") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Collection"); + + b.Navigation("Library"); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.MetadataID", b => @@ -600,6 +609,16 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .WithMany("ExternalIDs") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("People"); + + b.Navigation("Provider"); + + b.Navigation("Season"); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.PeopleRole", b => @@ -615,6 +634,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("People"); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.ProviderLink", b => @@ -630,6 +653,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ParentID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Child"); + + b.Navigation("Parent"); }); modelBuilder.Entity("Kyoo.Models.Season", b => @@ -639,6 +666,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.ShowDE", b => @@ -646,6 +675,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasOne("Kyoo.Models.Studio", "Studio") .WithMany() .HasForeignKey("StudioID"); + + b.Navigation("Studio"); }); modelBuilder.Entity("Kyoo.Models.Track", b => @@ -655,6 +686,65 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("Kyoo.Models.CollectionDE", b => + { + b.Navigation("LibraryLinks"); + + b.Navigation("Links"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.GenreDE", b => + { + b.Navigation("Links"); + }); + + modelBuilder.Entity("Kyoo.Models.LibraryDE", b => + { + b.Navigation("Links"); + + b.Navigation("ProviderLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + }); + + modelBuilder.Entity("Kyoo.Models.ShowDE", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + + b.Navigation("GenreLinks"); + + b.Navigation("LibraryLinks"); + + b.Navigation("People"); + + b.Navigation("Seasons"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210128212212_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.cs similarity index 73% rename from Kyoo/Models/DatabaseMigrations/Internal/20210128212212_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.cs index 04349e48..e15d9e45 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210128212212_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -namespace Kyoo.Models.DatabaseMigrations.Internal +namespace Kyoo.Kyoo.Models.DatabaseMigrations.Internal { public partial class Initial : Migration { @@ -17,12 +17,12 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Collections", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Slug = table.Column(nullable: false), - Name = table.Column(nullable: true), - Poster = table.Column(nullable: true), - Overview = table.Column(nullable: true) + Slug = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: true), + Poster = table.Column(type: "text", nullable: true), + Overview = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -33,10 +33,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Genres", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Slug = table.Column(nullable: false), - Name = table.Column(nullable: true) + Slug = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -47,10 +47,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Libraries", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Slug = table.Column(nullable: false), - Name = table.Column(nullable: true), + Slug = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: true), Paths = table.Column(type: "text[]", nullable: true) }, constraints: table => @@ -62,11 +62,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "People", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Slug = table.Column(nullable: false), - Name = table.Column(nullable: true), - Poster = table.Column(nullable: true) + Slug = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: true), + Poster = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -77,11 +77,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Providers", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Slug = table.Column(nullable: false), - Name = table.Column(nullable: true), - Logo = table.Column(nullable: true) + Slug = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: true), + Logo = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -92,10 +92,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Studios", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Slug = table.Column(nullable: false), - Name = table.Column(nullable: true) + Slug = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -106,46 +106,46 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "ProviderLinks", columns: table => new { - ParentID = table.Column(nullable: false), - ChildID = table.Column(nullable: false) + ParentID = table.Column(type: "integer", nullable: false), + ChildID = table.Column(type: "integer", nullable: false) }, constraints: table => { table.PrimaryKey("PK_ProviderLinks", x => new { x.ParentID, x.ChildID }); - table.ForeignKey( - name: "FK_ProviderLinks_Providers_ChildID", - column: x => x.ChildID, - principalTable: "Providers", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); table.ForeignKey( name: "FK_ProviderLinks_Libraries_ParentID", column: x => x.ParentID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProviderLinks_Providers_ChildID", + column: x => x.ChildID, + principalTable: "Providers", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( name: "Shows", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Slug = table.Column(nullable: false), - Title = table.Column(nullable: true), + Slug = table.Column(type: "text", nullable: false), + Title = table.Column(type: "text", nullable: true), Aliases = table.Column(type: "text[]", nullable: true), - Path = table.Column(nullable: true), - Overview = table.Column(nullable: true), - Status = table.Column(nullable: true), - TrailerUrl = table.Column(nullable: true), - StartYear = table.Column(nullable: true), - EndYear = table.Column(nullable: true), - Poster = table.Column(nullable: true), - Logo = table.Column(nullable: true), - Backdrop = table.Column(nullable: true), - IsMovie = table.Column(nullable: false), - StudioID = table.Column(nullable: true) + Path = table.Column(type: "text", nullable: true), + Overview = table.Column(type: "text", nullable: true), + Status = table.Column(type: "integer", nullable: true), + TrailerUrl = table.Column(type: "text", nullable: true), + StartYear = table.Column(type: "integer", nullable: true), + EndYear = table.Column(type: "integer", nullable: true), + Poster = table.Column(type: "text", nullable: true), + Logo = table.Column(type: "text", nullable: true), + Backdrop = table.Column(type: "text", nullable: true), + IsMovie = table.Column(type: "boolean", nullable: false), + StudioID = table.Column(type: "integer", nullable: true) }, constraints: table => { @@ -162,32 +162,32 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "CollectionLinks", columns: table => new { - ParentID = table.Column(nullable: false), - ChildID = table.Column(nullable: false) + ParentID = table.Column(type: "integer", nullable: false), + ChildID = table.Column(type: "integer", nullable: false) }, constraints: table => { table.PrimaryKey("PK_CollectionLinks", x => new { x.ParentID, x.ChildID }); - table.ForeignKey( - name: "FK_CollectionLinks_Shows_ChildID", - column: x => x.ChildID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); table.ForeignKey( name: "FK_CollectionLinks_Collections_ParentID", column: x => x.ParentID, principalTable: "Collections", principalColumn: "ID", onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionLinks_Shows_ChildID", + column: x => x.ChildID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( name: "GenreLinks", columns: table => new { - ParentID = table.Column(nullable: false), - ChildID = table.Column(nullable: false) + ParentID = table.Column(type: "integer", nullable: false), + ChildID = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -210,11 +210,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "LibraryLinks", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - LibraryID = table.Column(nullable: false), - ShowID = table.Column(nullable: true), - CollectionID = table.Column(nullable: true) + LibraryID = table.Column(type: "integer", nullable: false), + ShowID = table.Column(type: "integer", nullable: true), + CollectionID = table.Column(type: "integer", nullable: true) }, constraints: table => { @@ -243,12 +243,12 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "PeopleRoles", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - PeopleID = table.Column(nullable: false), - ShowID = table.Column(nullable: false), - Role = table.Column(nullable: true), - Type = table.Column(nullable: true) + PeopleID = table.Column(type: "integer", nullable: false), + ShowID = table.Column(type: "integer", nullable: false), + Role = table.Column(type: "text", nullable: true), + Type = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -271,14 +271,14 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Seasons", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ShowID = table.Column(nullable: false), - SeasonNumber = table.Column(nullable: false), - Title = table.Column(nullable: true), - Overview = table.Column(nullable: true), - Year = table.Column(nullable: true), - Poster = table.Column(nullable: true) + ShowID = table.Column(type: "integer", nullable: false), + SeasonNumber = table.Column(type: "integer", nullable: false), + Title = table.Column(type: "text", nullable: true), + Overview = table.Column(type: "text", nullable: true), + Year = table.Column(type: "integer", nullable: true), + Poster = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -295,19 +295,19 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Episodes", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ShowID = table.Column(nullable: false), - SeasonID = table.Column(nullable: true), - SeasonNumber = table.Column(nullable: false), - EpisodeNumber = table.Column(nullable: false), - AbsoluteNumber = table.Column(nullable: false), - Path = table.Column(nullable: true), - Title = table.Column(nullable: true), - Overview = table.Column(nullable: true), - ReleaseDate = table.Column(nullable: true), - Runtime = table.Column(nullable: false), - Poster = table.Column(nullable: true) + ShowID = table.Column(type: "integer", nullable: false), + SeasonID = table.Column(type: "integer", nullable: true), + SeasonNumber = table.Column(type: "integer", nullable: false), + EpisodeNumber = table.Column(type: "integer", nullable: false), + AbsoluteNumber = table.Column(type: "integer", nullable: false), + Path = table.Column(type: "text", nullable: true), + Title = table.Column(type: "text", nullable: true), + Overview = table.Column(type: "text", nullable: true), + ReleaseDate = table.Column(type: "timestamp without time zone", nullable: true), + Runtime = table.Column(type: "integer", nullable: false), + Poster = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -330,15 +330,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "MetadataIds", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ProviderID = table.Column(nullable: false), - ShowID = table.Column(nullable: true), - EpisodeID = table.Column(nullable: true), - SeasonID = table.Column(nullable: true), - PeopleID = table.Column(nullable: true), - DataID = table.Column(nullable: true), - Link = table.Column(nullable: true) + ProviderID = table.Column(type: "integer", nullable: false), + ShowID = table.Column(type: "integer", nullable: true), + EpisodeID = table.Column(type: "integer", nullable: true), + SeasonID = table.Column(type: "integer", nullable: true), + PeopleID = table.Column(type: "integer", nullable: true), + DataID = table.Column(type: "text", nullable: true), + Link = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -379,17 +379,17 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Tracks", columns: table => new { - ID = table.Column(nullable: false) + ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Title = table.Column(nullable: true), - Language = table.Column(nullable: true), - Codec = table.Column(nullable: true), - Path = table.Column(nullable: true), - Type = table.Column(nullable: false), - EpisodeID = table.Column(nullable: false), - IsDefault = table.Column(nullable: false), - IsForced = table.Column(nullable: false), - IsExternal = table.Column(nullable: false) + EpisodeID = table.Column(type: "integer", nullable: false), + IsDefault = table.Column(type: "boolean", nullable: false), + IsForced = table.Column(type: "boolean", nullable: false), + IsExternal = table.Column(type: "boolean", nullable: false), + Title = table.Column(type: "text", nullable: true), + Language = table.Column(type: "text", nullable: true), + Codec = table.Column(type: "text", nullable: true), + Path = table.Column(type: "text", nullable: true), + Type = table.Column(type: "integer", nullable: false) }, constraints: table => { @@ -446,11 +446,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal table: "LibraryLinks", column: "CollectionID"); - migrationBuilder.CreateIndex( - name: "IX_LibraryLinks_ShowID", - table: "LibraryLinks", - column: "ShowID"); - migrationBuilder.CreateIndex( name: "IX_LibraryLinks_LibraryID_CollectionID", table: "LibraryLinks", @@ -463,6 +458,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal columns: new[] { "LibraryID", "ShowID" }, unique: true); + migrationBuilder.CreateIndex( + name: "IX_LibraryLinks_ShowID", + table: "LibraryLinks", + column: "ShowID"); + migrationBuilder.CreateIndex( name: "IX_MetadataIds_EpisodeID", table: "MetadataIds", @@ -577,10 +577,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "People"); migrationBuilder.DropTable( - name: "Providers"); + name: "Libraries"); migrationBuilder.DropTable( - name: "Libraries"); + name: "Providers"); migrationBuilder.DropTable( name: "Episodes"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index ac3e38f6..c34b0309 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -1,13 +1,12 @@ // using System; -using System.Collections.Generic; using Kyoo; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -namespace Kyoo.Models.DatabaseMigrations.Internal +namespace Kyoo.Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] partial class DatabaseContextModelSnapshot : ModelSnapshot @@ -16,12 +15,12 @@ namespace Kyoo.Models.DatabaseMigrations.Internal { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Npgsql:Enum:item_type", "show,movie,collection") - .HasAnnotation("Npgsql:Enum:status", "finished,airing,planned,unknown") - .HasAnnotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,font") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) + .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) + .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); modelBuilder.Entity("Kyoo.Models.CollectionDE", b => { @@ -49,8 +48,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .IsUnique(); b.ToTable("Collections"); - - b.HasDiscriminator(); }); modelBuilder.Entity("Kyoo.Models.CollectionLink", b => @@ -138,8 +135,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .IsUnique(); b.ToTable("Genres"); - - b.HasDiscriminator(); }); modelBuilder.Entity("Kyoo.Models.GenreLink", b => @@ -167,7 +162,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Name") .HasColumnType("text"); - b.Property>("Paths") + b.Property("Paths") .HasColumnType("text[]"); b.Property("Slug") @@ -180,8 +175,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .IsUnique(); b.ToTable("Libraries"); - - b.HasDiscriminator(); }); modelBuilder.Entity("Kyoo.Models.LibraryLink", b => @@ -391,7 +384,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property>("Aliases") + b.Property("Aliases") .HasColumnType("text[]"); b.Property("Backdrop") @@ -442,8 +435,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("StudioID"); b.ToTable("Shows"); - - b.HasDiscriminator(); }); modelBuilder.Entity("Kyoo.Models.Studio", b => @@ -522,6 +513,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ParentID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Child"); + + b.Navigation("Parent"); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -535,6 +530,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Season"); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.GenreLink", b => @@ -550,6 +549,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ParentID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Child"); + + b.Navigation("Parent"); }); modelBuilder.Entity("Kyoo.Models.LibraryLink", b => @@ -569,6 +572,12 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .WithMany("LibraryLinks") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Collection"); + + b.Navigation("Library"); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.MetadataID", b => @@ -598,6 +607,16 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .WithMany("ExternalIDs") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("People"); + + b.Navigation("Provider"); + + b.Navigation("Season"); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.PeopleRole", b => @@ -613,6 +632,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("People"); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.ProviderLink", b => @@ -628,6 +651,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ParentID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Child"); + + b.Navigation("Parent"); }); modelBuilder.Entity("Kyoo.Models.Season", b => @@ -637,6 +664,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Show"); }); modelBuilder.Entity("Kyoo.Models.ShowDE", b => @@ -644,6 +673,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasOne("Kyoo.Models.Studio", "Studio") .WithMany() .HasForeignKey("StudioID"); + + b.Navigation("Studio"); }); modelBuilder.Entity("Kyoo.Models.Track", b => @@ -653,6 +684,65 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("EpisodeID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("Kyoo.Models.CollectionDE", b => + { + b.Navigation("LibraryLinks"); + + b.Navigation("Links"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.GenreDE", b => + { + b.Navigation("Links"); + }); + + modelBuilder.Entity("Kyoo.Models.LibraryDE", b => + { + b.Navigation("Links"); + + b.Navigation("ProviderLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + }); + + modelBuilder.Entity("Kyoo.Models.ShowDE", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + + b.Navigation("GenreLinks"); + + b.Navigation("LibraryLinks"); + + b.Navigation("People"); + + b.Navigation("Seasons"); }); #pragma warning restore 612, 618 } diff --git a/Kyoo/Models/IdentityDatabase.cs b/Kyoo/Models/IdentityDatabase.cs index 99cc5384..f70f07b3 100644 --- a/Kyoo/Models/IdentityDatabase.cs +++ b/Kyoo/Models/IdentityDatabase.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Options; namespace Kyoo { + // The configuration's database is named ConfigurationDbContext. public class IdentityDatabase : IdentityDbContext, IPersistedGrantDbContext { private readonly IOptions _operationalStoreOptions; diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 948bdb7a..ea3aabcd 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -146,8 +146,8 @@ namespace Kyoo services.AddScoped(); services.AddScoped(); services.AddScoped(); - - // services.AddScoped(); + + services.AddScoped(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/Kyoo/Tasks/CreateDatabase.cs b/Kyoo/Tasks/CreateDatabase.cs index bd372ae8..2264b116 100644 --- a/Kyoo/Tasks/CreateDatabase.cs +++ b/Kyoo/Tasks/CreateDatabase.cs @@ -28,9 +28,9 @@ namespace Kyoo.Tasks IdentityDatabase identityDatabase = serviceScope.ServiceProvider.GetService(); ConfigurationDbContext identityContext = serviceScope.ServiceProvider.GetService(); - databaseContext.Database.Migrate(); - identityDatabase.Database.Migrate(); - identityContext.Database.Migrate(); + databaseContext!.Database.Migrate(); + identityDatabase!.Database.Migrate(); + identityContext!.Database.Migrate(); if (!identityContext.Clients.Any()) { From 07aa4d67522685a3d1d6e85f50cc24fc752494d7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Feb 2021 23:05:31 +0100 Subject: [PATCH 06/54] Fixing LibraryManager dependecy error --- .../Controllers/Implementations}/ALibraryManager.cs | 4 ++-- Kyoo.Common/Kyoo.Common.csproj | 4 ---- Kyoo.CommonAPI/LibraryManager.cs | 4 ++-- Kyoo/Startup.cs | 1 + 4 files changed, 5 insertions(+), 8 deletions(-) rename {Kyoo.CommonAPI => Kyoo.Common/Controllers/Implementations}/ALibraryManager.cs (99%) diff --git a/Kyoo.CommonAPI/ALibraryManager.cs b/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs similarity index 99% rename from Kyoo.CommonAPI/ALibraryManager.cs rename to Kyoo.Common/Controllers/Implementations/ALibraryManager.cs index d809d334..8b276616 100644 --- a/Kyoo.CommonAPI/ALibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs @@ -6,7 +6,7 @@ using Kyoo.Models; namespace Kyoo.Controllers { - public class LibraryManager : ILibraryManager + public class ALibraryManager : ILibraryManager { public ILibraryRepository LibraryRepository { get; } public ILibraryItemRepository LibraryItemRepository { get; } @@ -20,7 +20,7 @@ namespace Kyoo.Controllers public IPeopleRepository PeopleRepository { get; } public IProviderRepository ProviderRepository { get; } - protected LibraryManager(ILibraryRepository libraryRepository, + protected ALibraryManager(ILibraryRepository libraryRepository, ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index 44f198b9..ff7ef1e2 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -23,8 +23,4 @@ - - - - diff --git a/Kyoo.CommonAPI/LibraryManager.cs b/Kyoo.CommonAPI/LibraryManager.cs index 5df48c89..ecc38f5d 100644 --- a/Kyoo.CommonAPI/LibraryManager.cs +++ b/Kyoo.CommonAPI/LibraryManager.cs @@ -8,11 +8,11 @@ using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Kyoo.Controllers { - public class TLibraryManager : LibraryManager + public class LibraryManager : ALibraryManager { private readonly DbContext _database; - public TLibraryManager(ILibraryRepository libraryRepository, + public LibraryManager(ILibraryRepository libraryRepository, ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index ea3aabcd..b04a7f18 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -146,6 +146,7 @@ namespace Kyoo services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddSingleton(); From 8597cf34f298d4f1ad162a1ac3e93bbede96b8b2 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 17 Feb 2021 01:09:49 +0100 Subject: [PATCH 07/54] Making a better RunGeneric method with more checks & more exceptions --- Kyoo.Common/Utility.cs | 71 +++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 01a0c366..de63e968 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -228,6 +228,49 @@ namespace Kyoo : type.GetInheritanceTree(); return types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); } + + public static IEnumerable IfEmpty(this IEnumerable self, Action action) + { + using IEnumerator enumerator = self.GetEnumerator(); + + if (!enumerator.MoveNext()) + { + action(); + yield break; + } + while (enumerator.MoveNext()) + yield return enumerator.Current; + } + + private static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args) + { + MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.Name == name) + .Where(x => x.GetGenericArguments().Length == generics.Length) + .Where(x => x.GetParameters().Length == args.Length) + .IfEmpty(() => throw new NullReferenceException($"A method named {name} with " + + $"{args.Length} arguments and {generics.Length} generic " + + $"types could not be found on {type.Name}.")) + .Where(x => + { + int i = 0; + // TODO this thing does not work. + return x.GetGenericArguments().All(y => y.IsAssignableFrom(generics[i++])); + }) + .IfEmpty(() => throw new NullReferenceException($"No method {name} match the generics specified.")) + .Where(x => + { + int i = 0; + return x.GetParameters().All(y => y.ParameterType == args[i++].GetType()); + }) + .IfEmpty(() => throw new NullReferenceException($"No method {name} match the parameters's types.")) + .Take(2) + .ToArray(); + + if (methods.Length == 1) + return methods[0]; + throw new NullReferenceException($"Multiple methods named {name} match the generics and parameters constraints."); + } public static T RunGenericMethod( [NotNull] Type owner, @@ -252,29 +295,33 @@ namespace Kyoo 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 = owner.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .SingleOrDefault(x => x.Name == methodName && x.GetParameters().Length == args.Length); - if (method == null) - throw new NullReferenceException($"A method named {methodName} with {args.Length} arguments could not be found on {owner.FullName}"); + MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args); return (T)method.MakeGenericMethod(types).Invoke(null, args?.ToArray()); } - + + public static T RunGenericMethod( + [NotNull] object instance, + [NotNull] string methodName, + [NotNull] Type type, + params object[] args) + { + return RunGenericMethod(instance, methodName, new[] {type}, args); + } + public static T RunGenericMethod( [NotNull] object instance, [NotNull] string methodName, - [NotNull] Type type, + [NotNull] Type[] types, params object[] args) { if (instance == null) throw new ArgumentNullException(nameof(instance)); if (methodName == null) throw new ArgumentNullException(nameof(methodName)); - if (type == null) - throw new ArgumentNullException(nameof(type)); - MethodInfo method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - if (method == null) - throw new NullReferenceException($"A method named {methodName} could not be found on {instance.GetType().FullName}"); - return (T)method.MakeGenericMethod(type).Invoke(instance, args?.ToArray()); + 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()); } [NotNull] From 3e0c0db79c171ffdad741a8f95e607ff3c19366d Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 17 Feb 2021 22:53:31 +0100 Subject: [PATCH 08/54] Fixing the run generic method --- Kyoo.Common/Utility.cs | 3 ++- Kyoo.CommonAPI/LibraryManager.cs | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index de63e968..dbbc400c 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -238,6 +238,8 @@ namespace Kyoo action(); yield break; } + + yield return enumerator.Current; while (enumerator.MoveNext()) yield return enumerator.Current; } @@ -254,7 +256,6 @@ namespace Kyoo .Where(x => { int i = 0; - // TODO this thing does not work. return x.GetGenericArguments().All(y => y.IsAssignableFrom(generics[i++])); }) .IfEmpty(() => throw new NullReferenceException($"No method {name} match the generics specified.")) diff --git a/Kyoo.CommonAPI/LibraryManager.cs b/Kyoo.CommonAPI/LibraryManager.cs index ecc38f5d..a6657be8 100644 --- a/Kyoo.CommonAPI/LibraryManager.cs +++ b/Kyoo.CommonAPI/LibraryManager.cs @@ -51,6 +51,8 @@ namespace Kyoo.Controllers if (!typeof(IEnumerable).IsAssignableFrom(typeof(T2))) return entry.Reference(member).LoadAsync(); + // TODO This is totally the wrong thing. We should run entry.Collection(collectionMember).LoadAsync() + // TODO where collectionMember would be member with T2 replaced by it's inner type (IEnumerable) Type collectionType = Utility.GetGenericDefinition(typeof(T2), typeof(IEnumerable<>)); return Utility.RunGenericMethod(entry, "Collection", collectionType, member).LoadAsync(); } From 56c7339816ec2799634ea6c5592c3b5a04782d3e Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 19 Feb 2021 22:22:57 +0100 Subject: [PATCH 09/54] Fixing the loader --- Kyoo.Common/Controllers/ILibraryManager.cs | 4 ++++ .../Implementations/ALibraryManager.cs | 10 ++++++++- Kyoo.CommonAPI/LibraryManager.cs | 22 +++++++++---------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 2cdbaf50..cd763d84 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -65,6 +65,10 @@ namespace Kyoo.Controllers where T : class, IResource where T2 : class; + Task Load([NotNull] T obj, Expression>> member) + where T : class, IResource + where T2 : class; + // Library Items relations Task> GetItemsFromLibrary(int id, Expression> where = null, diff --git a/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs b/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs index 8b276616..d2988241 100644 --- a/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs @@ -239,9 +239,17 @@ namespace Kyoo.Controllers where T : class, IResource where T2 : class { - return Task.CompletedTask; + // TODO figure out why setting this method as abstract prevent the app from loading this assembly. + throw new NotImplementedException(); } + public virtual Task Load(T obj, Expression>> member) + where T : class, IResource + where T2 : class + { + throw new NotImplementedException(); + } + public Task> GetLibraries(Expression> where = null, Sort sort = default, Pagination page = default) diff --git a/Kyoo.CommonAPI/LibraryManager.cs b/Kyoo.CommonAPI/LibraryManager.cs index a6657be8..5bc2559d 100644 --- a/Kyoo.CommonAPI/LibraryManager.cs +++ b/Kyoo.CommonAPI/LibraryManager.cs @@ -1,10 +1,8 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; namespace Kyoo.Controllers { @@ -39,22 +37,22 @@ namespace Kyoo.Controllers _database = database; } + public override Task Load(T obj, Expression>> member) + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + if (!Utility.IsPropertyExpression(member) || member == null) + throw new ArgumentException($"{nameof(member)} is not a property."); + return _database.Entry(obj).Collection(member).LoadAsync(); + } + public override Task Load(T obj, Expression> member) { if (obj == null) throw new ArgumentNullException(nameof(obj)); if (!Utility.IsPropertyExpression(member) || member == null) throw new ArgumentException($"{nameof(member)} is not a property."); - - EntityEntry entry = _database.Entry(obj); - - if (!typeof(IEnumerable).IsAssignableFrom(typeof(T2))) - return entry.Reference(member).LoadAsync(); - - // TODO This is totally the wrong thing. We should run entry.Collection(collectionMember).LoadAsync() - // TODO where collectionMember would be member with T2 replaced by it's inner type (IEnumerable) - Type collectionType = Utility.GetGenericDefinition(typeof(T2), typeof(IEnumerable<>)); - return Utility.RunGenericMethod(entry, "Collection", collectionType, member).LoadAsync(); + return _database.Entry(obj).Reference(member).LoadAsync(); } } } \ No newline at end of file From 14e4772dd1ab1e544ec7acb1b2f668a52f8a1307 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 21 Feb 2021 16:00:09 +0100 Subject: [PATCH 10/54] Recreating the Load method using repositories and pattern matching --- Kyoo.Common/Controllers/ILibraryManager.cs | 6 +- Kyoo.Common/Controllers/IRepository.cs | 29 +++++++-- .../{ALibraryManager.cs => LibraryManager.cs} | 60 ++++++++++++++++--- Kyoo.Common/ExpressionRewrite.cs | 13 ++-- Kyoo.Common/Utility.cs | 14 ++++- Kyoo.CommonAPI/LibraryManager.cs | 58 ------------------ 6 files changed, 97 insertions(+), 83 deletions(-) rename Kyoo.Common/Controllers/Implementations/{ALibraryManager.cs => LibraryManager.cs} (84%) delete mode 100644 Kyoo.CommonAPI/LibraryManager.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index cd763d84..f6d746c3 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -61,11 +61,11 @@ namespace Kyoo.Controllers Task GetStudio(Expression> where); Task GetPerson(Expression> where); - Task Load([NotNull] T obj, Expression> member) + Task Load([NotNull] T obj, [CanBeNull] Expression> member) where T : class, IResource - where T2 : class; + where T2 : class, IResource; - Task Load([NotNull] T obj, Expression>> member) + Task Load([NotNull] T obj, [CanBeNull] Expression>> member) where T : class, IResource where T2 : class; diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 522154c3..41729858 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -20,7 +20,7 @@ namespace Kyoo.Controllers AfterID = afterID; } - public static implicit operator Pagination(int limit) => new Pagination(limit); + public static implicit operator Pagination(int limit) => new(limit); } public struct Sort @@ -67,7 +67,7 @@ namespace Kyoo.Controllers public Sort To() { - return new Sort(Key.Convert>(), Descendant); + return new(Key.Convert>(), Descendant); } } @@ -206,6 +206,27 @@ namespace Kyoo.Controllers Pagination limit = default ) => GetFromPeople(showSlug, where, new Sort(sort), limit); } - - public interface IProviderRepository : IRepository {} + + public interface IProviderRepository : IRepository + { + Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(int showID, + [Optional] Expression> where, + Expression> sort = default, + Pagination limit = default + ) => GetFromShow(showID, where, new Sort(sort), limit); + + Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort = default, + Pagination limit = default + ) => GetFromShow(showSlug, where, new Sort(sort), limit); + } } diff --git a/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs similarity index 84% rename from Kyoo.Common/Controllers/Implementations/ALibraryManager.cs rename to Kyoo.Common/Controllers/Implementations/LibraryManager.cs index d2988241..8fe32489 100644 --- a/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -6,7 +6,7 @@ using Kyoo.Models; namespace Kyoo.Controllers { - public class ALibraryManager : ILibraryManager + public class LibraryManager : ILibraryManager { public ILibraryRepository LibraryRepository { get; } public ILibraryItemRepository LibraryItemRepository { get; } @@ -20,7 +20,7 @@ namespace Kyoo.Controllers public IPeopleRepository PeopleRepository { get; } public IProviderRepository ProviderRepository { get; } - protected ALibraryManager(ILibraryRepository libraryRepository, + protected LibraryManager(ILibraryRepository libraryRepository, ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, @@ -235,19 +235,63 @@ namespace Kyoo.Controllers return PeopleRepository.Get(where); } - public virtual Task Load(T obj, Expression> member) + public async Task Load(T obj, Expression> member) where T : class, IResource - where T2 : class + where T2 : class, IResource { - // TODO figure out why setting this method as abstract prevent the app from loading this assembly. - throw new NotImplementedException(); + dynamic identifier = obj.ID > 0 ? obj.ID : obj.Slug; + switch (obj, (T2)default) + { + case (Show s, Studio): s.Studio = await StudioRepository.GetFromShow(identifier); break; + + case (Season s, Show): s.Show = await ShowRepository.GetFromSeason(identifier); break; + + case (Episode e, Show): e.Show = await ShowRepository.GetFromEpisode(identifier); break; + case (Episode e, Season): e.Season = await SeasonRepository.GetFromEpisode(identifier); break; + + case (Track t, Episode): t.Episode = await EpisodeRepository.GetFromTrack(identifier); break; + + default: throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}."); + } } - public virtual Task Load(T obj, Expression>> member) + public async Task Load(T obj, Expression>> member) where T : class, IResource where T2 : class { - throw new NotImplementedException(); + dynamic identifier = obj.ID > 0 ? obj.ID : obj.Slug; + switch (obj, (T2)default) + { + case (Library l, ProviderID): l.Providers = await ProviderRepository.GetFromLibrary(identifier); break; + case (Library l, Show): l.Shows = await ShowRepository.GetFromLibrary(identifier); break; + case (Library l, Collection): l.Collections = await CollectionRepository.GetFromLibrary(identifier); break; + + case (Collection c, Show): c.Shows = await ShowRepository.GetFromCollection(identifier); break; + case (Collection c, Library): c.Libraries = await LibraryRepository.GetFromCollection(identifier); break; + + case (Show s, MetadataID): s.ExternalIDs = await ProviderRepository.GetFromShow(identifier); break; + case (Show s, Genre): s.Genres = await GenreRepository.GetFromShow(identifier); break; + case (Show s, PeopleRole): s.People = await PeopleRepository.GetFromShow(identifier); break; + case (Show s, Season): s.Seasons = await SeasonRepository.GetFromShow(identifier); break; + case (Show s, Episode): s.Episodes = await EpisodeRepository.GetFromShow(identifier); break; + case (Show s, Library): s.Libraries = await LibraryRepository.GetFromShow(identifier); break; + case (Show s, Collection): s.Collections = await CollectionRepository.GetFromShow(identifier); break; + + case (Season s, MetadataID): s.ExternalIDs = await ProviderRepository.GetFromSeason(identifier); break; + case (Season s, Episode): s.Episodes = await EpisodeRepository.GetFromSeason(identifier); break; + + case (Episode e, MetadataID): e.ExternalIDs = await ProviderRepository.GetFromEpisode(identifier); break; + case (Episode e, Track): e.Tracks = await TrackRepository.GetFromEpisode(identifier); break; + + case (Genre g, Show): g.Shows = await ShowRepository.GetFromGenre(identifier); break; + + case (Studio s, Show): s.Shows = await ShowRepository.GetFromStudio(identifier); break; + + case (People p, MetadataID): p.ExternalIDs = await ProviderRepository.GetFromPeople(identifier); break; + case (People p, PeopleRole): p.Roles = await ShowRepository.GetFromPeople(identifier); break; + + default: throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}."); + }; } public Task> GetLibraries(Expression> where = null, diff --git a/Kyoo.Common/ExpressionRewrite.cs b/Kyoo.Common/ExpressionRewrite.cs index c7db9636..3bb5bc5c 100644 --- a/Kyoo.Common/ExpressionRewrite.cs +++ b/Kyoo.Common/ExpressionRewrite.cs @@ -43,26 +43,21 @@ namespace Kyoo (string inner, _, ParameterExpression p) = _innerRewrites.FirstOrDefault(x => x.param == node.Expression); if (inner != null) { - Expression param = p; - foreach (string accessor in inner.Split('.')) - param = Expression.Property(param, accessor); + Expression param = inner.Split('.').Aggregate(p, Expression.Property); node = Expression.Property(param, node.Member.Name); } // Can't use node.Member directly because we want to support attribute override - MemberInfo member = node.Expression.Type.GetProperty(node.Member.Name) ?? node.Member; + MemberInfo member = node.Expression!.Type.GetProperty(node.Member.Name) ?? node.Member; ExpressionRewriteAttribute attr = member!.GetCustomAttribute(); if (attr == null) return base.VisitMember(node); - Expression property = node.Expression; - foreach (string child in attr.Link.Split('.')) - property = Expression.Property(property, child); - + Expression property = attr.Link.Split('.').Aggregate(node.Expression, Expression.Property); if (property is MemberExpression expr) Visit(expr.Expression); _inner = attr.Inner; - return property; + return property!; } protected override Expression VisitLambda(Expression node) diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index dbbc400c..bb627d0b 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -17,7 +17,7 @@ namespace Kyoo { public static class Utility { - public static bool IsPropertyExpression(Expression> ex) + public static bool IsPropertyExpression(LambdaExpression ex) { return ex == null || ex.Body is MemberExpression || @@ -490,6 +490,18 @@ namespace Kyoo return first.SequenceEqual(second, new LinkComparer()); } + public static string GetMemberName([NotNull] Expression expression) + { + var t = ExpressionRewrite.Rewrite(expression); + return ExpressionRewrite.Rewrite(expression) switch + { + MemberExpression member => member.Member.Name, + LambdaExpression lambda when IsPropertyExpression(lambda) => GetMemberName(lambda.Body), + null => throw new ArgumentNullException(nameof(expression)), + _ => throw new ArgumentException($"Can't get member.") + }; + } + public static Expression Convert([CanBeNull] this Expression expr) where T : Delegate { diff --git a/Kyoo.CommonAPI/LibraryManager.cs b/Kyoo.CommonAPI/LibraryManager.cs deleted file mode 100644 index 5bc2559d..00000000 --- a/Kyoo.CommonAPI/LibraryManager.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; - -namespace Kyoo.Controllers -{ - public class LibraryManager : ALibraryManager - { - private readonly DbContext _database; - - public LibraryManager(ILibraryRepository libraryRepository, - ILibraryItemRepository libraryItemRepository, - ICollectionRepository collectionRepository, - IShowRepository showRepository, - ISeasonRepository seasonRepository, - IEpisodeRepository episodeRepository, - ITrackRepository trackRepository, - IGenreRepository genreRepository, - IStudioRepository studioRepository, - IProviderRepository providerRepository, - IPeopleRepository peopleRepository, - DbContext database) - : base(libraryRepository, - libraryItemRepository, - collectionRepository, - showRepository, - seasonRepository, - episodeRepository, - trackRepository, - genreRepository, - studioRepository, - providerRepository, - peopleRepository) - { - _database = database; - } - - public override Task Load(T obj, Expression>> member) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - if (!Utility.IsPropertyExpression(member) || member == null) - throw new ArgumentException($"{nameof(member)} is not a property."); - return _database.Entry(obj).Collection(member).LoadAsync(); - } - - public override Task Load(T obj, Expression> member) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - if (!Utility.IsPropertyExpression(member) || member == null) - throw new ArgumentException($"{nameof(member)} is not a property."); - return _database.Entry(obj).Reference(member).LoadAsync(); - } - } -} \ No newline at end of file From 438074a93e418e349e969eef10ac67c42f98a150 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 21 Feb 2021 17:40:38 +0100 Subject: [PATCH 11/54] Using where's argument of get all instead of a method in a repositroy --- .../Implementations/LibraryManager.cs | 98 ++++++++++++------- Kyoo.Common/Models/MetadataID.cs | 1 - Kyoo.Common/Utility.cs | 31 +++--- 3 files changed, 78 insertions(+), 52 deletions(-) diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 8fe32489..be012f1b 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; @@ -235,62 +236,83 @@ namespace Kyoo.Controllers return PeopleRepository.Get(where); } - public async Task Load(T obj, Expression> member) + public Task Load(T obj, Expression> member) where T : class, IResource where T2 : class, IResource { - dynamic identifier = obj.ID > 0 ? obj.ID : obj.Slug; - switch (obj, (T2)default) + return (obj, (T2)default) switch { - case (Show s, Studio): s.Studio = await StudioRepository.GetFromShow(identifier); break; - - case (Season s, Show): s.Show = await ShowRepository.GetFromSeason(identifier); break; - - case (Episode e, Show): e.Show = await ShowRepository.GetFromEpisode(identifier); break; - case (Episode e, Season): e.Season = await SeasonRepository.GetFromEpisode(identifier); break; - - case (Track t, Episode): t.Episode = await EpisodeRepository.GetFromTrack(identifier); break; - - default: throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}."); - } + (Show s, Studio) => StudioRepository.Get(x => x.Shows.Any(Utility.ResourceEqualsFunc(obj))) + .Then(x => s.Studio = x), + + (Season s, Show) => ShowRepository.Get(Utility.ResourceEquals(obj)).Then(x => s.Show = x), + + (Episode e, Show) => ShowRepository.Get(Utility.ResourceEquals(obj)).Then(x => e.Show = x), + (Episode e, Season) => SeasonRepository.Get(Utility.ResourceEquals(obj)) + .Then(x => e.Season = x), + + (Track t, Episode) => EpisodeRepository.Get(Utility.ResourceEquals(obj)) + .Then(x => t.Episode = x), + + _ => throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}.") + }; } - public async Task Load(T obj, Expression>> member) + public Task Load(T obj, Expression>> member) where T : class, IResource where T2 : class { - dynamic identifier = obj.ID > 0 ? obj.ID : obj.Slug; - switch (obj, (T2)default) + return (obj, (T2)default) switch { - case (Library l, ProviderID): l.Providers = await ProviderRepository.GetFromLibrary(identifier); break; - case (Library l, Show): l.Shows = await ShowRepository.GetFromLibrary(identifier); break; - case (Library l, Collection): l.Collections = await CollectionRepository.GetFromLibrary(identifier); break; + (Library l, ProviderID) => ProviderRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => l.Providers = x), + (Library l, Show) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => l.Shows = x), + (Library l, Collection) => CollectionRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => l.Collections = x), - case (Collection c, Show): c.Shows = await ShowRepository.GetFromCollection(identifier); break; - case (Collection c, Library): c.Libraries = await LibraryRepository.GetFromCollection(identifier); break; + (Collection c, Show) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => c.Shows = x), + (Collection c, Library) => LibraryRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => c.Libraries = x), - case (Show s, MetadataID): s.ExternalIDs = await ProviderRepository.GetFromShow(identifier); break; - case (Show s, Genre): s.Genres = await GenreRepository.GetFromShow(identifier); break; - case (Show s, PeopleRole): s.People = await PeopleRepository.GetFromShow(identifier); break; - case (Show s, Season): s.Seasons = await SeasonRepository.GetFromShow(identifier); break; - case (Show s, Episode): s.Episodes = await EpisodeRepository.GetFromShow(identifier); break; - case (Show s, Library): s.Libraries = await LibraryRepository.GetFromShow(identifier); break; - case (Show s, Collection): s.Collections = await CollectionRepository.GetFromShow(identifier); break; + (Show s, MetadataID) => ProviderRepository.Get(Utility.ResourceEquals(obj)) + .Then(x => s.ExternalIDs = x), + (Show s, Genre) => GenreRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => s.Genres = x), + (Show s, PeopleRole) => PeopleRepository.GetFromShow(Utility.ResourceEquals(obj)) + .Then(x => s.People = x), + (Show s, Season) => SeasonRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => s.Seasons = x), + (Show s, Episode) => EpisodeRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => s.Episodes = x), + (Show s, Library) => LibraryRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => s.Libraries = x), + (Show s, Collection) => CollectionRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => s.Collections = x), - case (Season s, MetadataID): s.ExternalIDs = await ProviderRepository.GetFromSeason(identifier); break; - case (Season s, Episode): s.Episodes = await EpisodeRepository.GetFromSeason(identifier); break; + (Season s, MetadataID) => ProviderRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => s.ExternalIDs = x), + (Season s, Episode) => EpisodeRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => s.Episodes = x), - case (Episode e, MetadataID): e.ExternalIDs = await ProviderRepository.GetFromEpisode(identifier); break; - case (Episode e, Track): e.Tracks = await TrackRepository.GetFromEpisode(identifier); break; + (Episode e, MetadataID) => ProviderRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => e.ExternalIDs = x), + (Episode e, Track) => TrackRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => e.Tracks = x), - case (Genre g, Show): g.Shows = await ShowRepository.GetFromGenre(identifier); break; + (Genre g, Show) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => g.Shows = x), - case (Studio s, Show): s.Shows = await ShowRepository.GetFromStudio(identifier); break; + (Studio s, Show) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => s.Shows = x), - case (People p, MetadataID): p.ExternalIDs = await ProviderRepository.GetFromPeople(identifier); break; - case (People p, PeopleRole): p.Roles = await ShowRepository.GetFromPeople(identifier); break; + (People p, MetadataID) => ProviderRepository.GetAll(Utility.ResourceEquals(obj)) + .Then(x => p.ExternalIDs = x), + (People p, PeopleRole) => PeopleRepository.GetFromPeople(Utility.ResourceEquals(obj)) + .Then(x => p.Roles = x), - default: throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}."); + _ => throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}.") }; } diff --git a/Kyoo.Common/Models/MetadataID.cs b/Kyoo.Common/Models/MetadataID.cs index 6ea84afa..945e578b 100644 --- a/Kyoo.Common/Models/MetadataID.cs +++ b/Kyoo.Common/Models/MetadataID.cs @@ -1,4 +1,3 @@ -using System; using Kyoo.Models.Attributes; namespace Kyoo.Models diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index bb627d0b..defa3f47 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -423,6 +423,22 @@ namespace Kyoo return (T)((dynamic)x).Result; }, TaskContinuationOptions.ExecuteSynchronously); } + + public static Expression> ResourceEquals(IResource obj) + where T : IResource + { + if (obj.ID > 0) + return x => x.ID == obj.ID || x.Slug == obj.Slug; + return x => x.Slug == obj.Slug; + } + + public static Func ResourceEqualsFunc(IResource obj) + where T : IResource + { + if (obj.ID > 0) + return x => x.ID == obj.ID || x.Slug == obj.Slug; + return x => x.Slug == obj.Slug; + } public static bool ResourceEquals([CanBeNull] object first, [CanBeNull] object second) { @@ -489,19 +505,8 @@ namespace Kyoo return false; return first.SequenceEqual(second, new LinkComparer()); } - - public static string GetMemberName([NotNull] Expression expression) - { - var t = ExpressionRewrite.Rewrite(expression); - return ExpressionRewrite.Rewrite(expression) switch - { - MemberExpression member => member.Member.Name, - LambdaExpression lambda when IsPropertyExpression(lambda) => GetMemberName(lambda.Body), - null => throw new ArgumentNullException(nameof(expression)), - _ => throw new ArgumentException($"Can't get member.") - }; - } - + + public static Expression Convert([CanBeNull] this Expression expr) where T : Delegate { From 466873da9f2a49d1cf4ed2dbf4fde46a0d93f481 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 21 Feb 2021 18:37:02 +0100 Subject: [PATCH 12/54] Finishing the Load method --- Kyoo.Common/Controllers/IRepository.cs | 21 +++++-------------- .../Implementations/LibraryManager.cs | 16 +++++++------- .../Repositories/ProviderRepository.cs | 12 +++++++++++ 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 41729858..21f3318a 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -209,24 +209,13 @@ namespace Kyoo.Controllers public interface IProviderRepository : IRepository { - Task> GetFromShow(int showID, - Expression> where = null, + Task> GetMetadataID(Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetFromShow(int showID, - [Optional] Expression> where, - Expression> sort = default, + + Task> GetMetadataID([Optional] Expression> where, + Expression> sort, Pagination limit = default - ) => GetFromShow(showID, where, new Sort(sort), limit); - - Task> GetFromShow(string showSlug, - Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetFromShow(string showSlug, - [Optional] Expression> where, - Expression> sort = default, - Pagination limit = default - ) => GetFromShow(showSlug, where, new Sort(sort), limit); + ) => GetMetadataID(where, new Sort(sort), limit); } } diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index be012f1b..1c3b7543 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -276,12 +276,12 @@ namespace Kyoo.Controllers (Collection c, Library) => LibraryRepository.GetAll(Utility.ResourceEquals(obj)) .Then(x => c.Libraries = x), - (Show s, MetadataID) => ProviderRepository.Get(Utility.ResourceEquals(obj)) + (Show s, MetadataID) => ProviderRepository.GetMetadataID(x => x.ShowID == obj.ID) .Then(x => s.ExternalIDs = x), (Show s, Genre) => GenreRepository.GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Genres = x), - (Show s, PeopleRole) => PeopleRepository.GetFromShow(Utility.ResourceEquals(obj)) - .Then(x => s.People = x), + (Show s, PeopleRole) => (PeopleRepository.GetFromShow((dynamic)(obj.ID > 0 ? obj.ID : obj.Slug)) + as Task>).Then(x => s.People = x), (Show s, Season) => SeasonRepository.GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Seasons = x), (Show s, Episode) => EpisodeRepository.GetAll(Utility.ResourceEquals(obj)) @@ -291,12 +291,12 @@ namespace Kyoo.Controllers (Show s, Collection) => CollectionRepository.GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Collections = x), - (Season s, MetadataID) => ProviderRepository.GetAll(Utility.ResourceEquals(obj)) + (Season s, MetadataID) => ProviderRepository.GetMetadataID(x => x.SeasonID == obj.ID) .Then(x => s.ExternalIDs = x), (Season s, Episode) => EpisodeRepository.GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Episodes = x), - (Episode e, MetadataID) => ProviderRepository.GetAll(Utility.ResourceEquals(obj)) + (Episode e, MetadataID) => ProviderRepository.GetMetadataID(x => x.EpisodeID == obj.ID) .Then(x => e.ExternalIDs = x), (Episode e, Track) => TrackRepository.GetAll(Utility.ResourceEquals(obj)) .Then(x => e.Tracks = x), @@ -307,10 +307,10 @@ namespace Kyoo.Controllers (Studio s, Show) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Shows = x), - (People p, MetadataID) => ProviderRepository.GetAll(Utility.ResourceEquals(obj)) + (People p, MetadataID) => ProviderRepository.GetMetadataID(x => x.PeopleID == obj.ID) .Then(x => p.ExternalIDs = x), - (People p, PeopleRole) => PeopleRepository.GetFromPeople(Utility.ResourceEquals(obj)) - .Then(x => p.Roles = x), + (People p, PeopleRole) => (PeopleRepository.GetFromPeople((dynamic)(obj.ID > 0 ? obj.ID : obj.Slug)) + as Task>).Then(x => p.Roles = x), _ => throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}.") }; diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 6219a919..9dd112c0 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -45,5 +45,17 @@ namespace Kyoo.Controllers // TODO handle ExternalID deletion when they refer to this providerID. await _database.SaveChangesAsync(); } + + public Task> GetMetadataID(Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return ApplyFilters(_database.MetadataIds, + x => _database.MetadataIds.FirstOrDefaultAsync(y => y.ID == x), + x => x.ID, + where, + sort, + limit); + } } } \ No newline at end of file From 49f0ab8bfa721c9214a99b15a464506d3b6d7623 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 21 Feb 2021 19:10:29 +0100 Subject: [PATCH 13/54] Fixing the null pattern match --- Kyoo.Common/Controllers/ILibraryManager.cs | 4 ++-- .../Controllers/Implementations/LibraryManager.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index f6d746c3..30129de8 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -63,11 +63,11 @@ namespace Kyoo.Controllers Task Load([NotNull] T obj, [CanBeNull] Expression> member) where T : class, IResource - where T2 : class, IResource; + where T2 : class, IResource, new(); Task Load([NotNull] T obj, [CanBeNull] Expression>> member) where T : class, IResource - where T2 : class; + where T2 : class, new(); // Library Items relations Task> GetItemsFromLibrary(int id, diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 1c3b7543..d239f401 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -21,7 +21,7 @@ namespace Kyoo.Controllers public IPeopleRepository PeopleRepository { get; } public IProviderRepository ProviderRepository { get; } - protected LibraryManager(ILibraryRepository libraryRepository, + public LibraryManager(ILibraryRepository libraryRepository, ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, @@ -238,9 +238,9 @@ namespace Kyoo.Controllers public Task Load(T obj, Expression> member) where T : class, IResource - where T2 : class, IResource + where T2 : class, IResource, new() { - return (obj, (T2)default) switch + return (obj, new T2()) switch { (Show s, Studio) => StudioRepository.Get(x => x.Shows.Any(Utility.ResourceEqualsFunc(obj))) .Then(x => s.Studio = x), @@ -260,9 +260,9 @@ namespace Kyoo.Controllers public Task Load(T obj, Expression>> member) where T : class, IResource - where T2 : class + where T2 : class, new() { - return (obj, (T2)default) switch + return (obj, new T2()) switch { (Library l, ProviderID) => ProviderRepository.GetAll(Utility.ResourceEquals(obj)) .Then(x => l.Providers = x), From 97ab1affa0b7a431f42982f066f0e155a764a481 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 27 Feb 2021 02:43:15 +0100 Subject: [PATCH 14/54] Adding a 'fields' query param to every requests to load related data --- Kyoo.Common/Controllers/ILibraryManager.cs | 13 +- .../Implementations/LibraryManager.cs | 114 ++++++++++++------ Kyoo.Common/Utility.cs | 20 ++- Kyoo.CommonAPI/CrudApi.cs | 10 +- Kyoo.CommonAPI/ResourceViewAttribute.cs | 60 +++++++++ Kyoo/Views/API/CollectionApi.cs | 16 --- Kyoo/Views/API/EpisodeApi.cs | 12 +- Kyoo/Views/API/GenreApi.cs | 8 -- Kyoo/Views/API/LibraryApi.cs | 24 ---- Kyoo/Views/API/LibraryItemApi.cs | 4 - Kyoo/Views/API/PeopleApi.cs | 8 -- Kyoo/Views/API/SeasonApi.cs | 12 -- Kyoo/Views/API/ShowApi.cs | 48 -------- Kyoo/Views/API/StudioApi.cs | 8 -- 14 files changed, 166 insertions(+), 191 deletions(-) create mode 100644 Kyoo.CommonAPI/ResourceViewAttribute.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 30129de8..f3f0910a 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -61,14 +61,19 @@ namespace Kyoo.Controllers Task GetStudio(Expression> where); Task GetPerson(Expression> where); - Task Load([NotNull] T obj, [CanBeNull] Expression> member) + Task Load([NotNull] T obj, Expression> member) where T : class, IResource where T2 : class, IResource, new(); - - Task Load([NotNull] T obj, [CanBeNull] Expression>> member) + + Task Load([NotNull] T obj, Expression>> member) where T : class, IResource where T2 : class, new(); - + + Task Load(T obj, string memberName) + where T : class, IResource; + + Task Load(IResource obj, string memberName); + // Library Items relations Task> GetItemsFromLibrary(int id, Expression> where = null, diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index d239f401..ad1554a6 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -236,83 +236,117 @@ namespace Kyoo.Controllers return PeopleRepository.Get(where); } - public Task Load(T obj, Expression> member) + public Task Load(T obj, Expression> member) where T : class, IResource where T2 : class, IResource, new() { - return (obj, new T2()) switch - { - (Show s, Studio) => StudioRepository.Get(x => x.Shows.Any(Utility.ResourceEqualsFunc(obj))) - .Then(x => s.Studio = x), - - (Season s, Show) => ShowRepository.Get(Utility.ResourceEquals(obj)).Then(x => s.Show = x), - - (Episode e, Show) => ShowRepository.Get(Utility.ResourceEquals(obj)).Then(x => e.Show = x), - (Episode e, Season) => SeasonRepository.Get(Utility.ResourceEquals(obj)) - .Then(x => e.Season = x), - - (Track t, Episode) => EpisodeRepository.Get(Utility.ResourceEquals(obj)) - .Then(x => t.Episode = x), - - _ => throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}.") - }; + if (member == null) + throw new ArgumentNullException(nameof(member)); + return Load(obj, Utility.GetPropertyName(member)); } - public Task Load(T obj, Expression>> member) + public Task Load(T obj, Expression>> member) where T : class, IResource where T2 : class, new() { - return (obj, new T2()) switch + if (member == null) + throw new ArgumentNullException(nameof(member)); + return Load(obj, Utility.GetPropertyName(member)); + } + + public async Task Load(T obj, string member) + where T : class, IResource + { + await Load(obj as IResource, member); + return obj; + } + + public Task Load(IResource obj, string member) + { + return (obj, member) switch { - (Library l, ProviderID) => ProviderRepository.GetAll(Utility.ResourceEquals(obj)) + (Library l, nameof(Library.Providers)) => ProviderRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => l.Providers = x), - (Library l, Show) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) + (Library l, nameof(Library.Shows)) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) .Then(x => l.Shows = x), - (Library l, Collection) => CollectionRepository.GetAll(Utility.ResourceEquals(obj)) + (Library l, nameof(Library.Collections)) => CollectionRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => l.Collections = x), - (Collection c, Show) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) + (Collection c, nameof(Library.Shows)) => ShowRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => c.Shows = x), - (Collection c, Library) => LibraryRepository.GetAll(Utility.ResourceEquals(obj)) + (Collection c, nameof(Collection.Libraries)) => LibraryRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => c.Libraries = x), - (Show s, MetadataID) => ProviderRepository.GetMetadataID(x => x.ShowID == obj.ID) + (Show s, nameof(Show.ExternalIDs)) => ProviderRepository + .GetMetadataID(x => x.ShowID == obj.ID) .Then(x => s.ExternalIDs = x), - (Show s, Genre) => GenreRepository.GetAll(Utility.ResourceEquals(obj)) + (Show s, nameof(Show.Genres)) => GenreRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Genres = x), - (Show s, PeopleRole) => (PeopleRepository.GetFromShow((dynamic)(obj.ID > 0 ? obj.ID : obj.Slug)) + (Show s, nameof(Show.People)) => ( + PeopleRepository.GetFromShow((dynamic)(obj.ID > 0 ? obj.ID : obj.Slug)) as Task>).Then(x => s.People = x), - (Show s, Season) => SeasonRepository.GetAll(Utility.ResourceEquals(obj)) + (Show s, nameof(Show.Seasons)) => SeasonRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Seasons = x), - (Show s, Episode) => EpisodeRepository.GetAll(Utility.ResourceEquals(obj)) + (Show s, nameof(Show.Episodes)) => EpisodeRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Episodes = x), - (Show s, Library) => LibraryRepository.GetAll(Utility.ResourceEquals(obj)) + (Show s, nameof(Show.Libraries)) => LibraryRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Libraries = x), - (Show s, Collection) => CollectionRepository.GetAll(Utility.ResourceEquals(obj)) + (Show s, nameof(Show.Collections)) => CollectionRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Collections = x), + (Show s, nameof(Show.Studio)) => StudioRepository + .Get(x => x.Shows.Any(Utility.ResourceEqualsFunc(obj))) + .Then(x => s.Studio = x), - (Season s, MetadataID) => ProviderRepository.GetMetadataID(x => x.SeasonID == obj.ID) + (Season s, nameof(Season.ExternalIDs)) => ProviderRepository + .GetMetadataID(x => x.SeasonID == obj.ID) .Then(x => s.ExternalIDs = x), - (Season s, Episode) => EpisodeRepository.GetAll(Utility.ResourceEquals(obj)) + (Season s, nameof(Season.Episodes)) => EpisodeRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Episodes = x), + (Season s, nameof(Season.Show)) => ShowRepository + .Get(Utility.ResourceEquals(obj)).Then(x => s.Show = x), - (Episode e, MetadataID) => ProviderRepository.GetMetadataID(x => x.EpisodeID == obj.ID) + (Episode e, nameof(Episode.ExternalIDs)) => ProviderRepository + .GetMetadataID(x => x.EpisodeID == obj.ID) .Then(x => e.ExternalIDs = x), - (Episode e, Track) => TrackRepository.GetAll(Utility.ResourceEquals(obj)) + (Episode e, nameof(Episode.Tracks)) => TrackRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => e.Tracks = x), + (Episode e, nameof(Episode.Show)) => ShowRepository + .Get(Utility.ResourceEquals(obj)).Then(x => e.Show = x), + (Episode e, nameof(Episode.Season)) => SeasonRepository + .Get(Utility.ResourceEquals(obj)) + .Then(x => e.Season = x), - (Genre g, Show) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) + (Track t, nameof(Track.Episode)) => EpisodeRepository + .Get(Utility.ResourceEquals(obj)) + .Then(x => t.Episode = x), + + (Genre g, nameof(Genre.Shows)) => ShowRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => g.Shows = x), - (Studio s, Show) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) + (Studio s, nameof(Studio.Shows)) => ShowRepository + .GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Shows = x), - (People p, MetadataID) => ProviderRepository.GetMetadataID(x => x.PeopleID == obj.ID) + (People p, nameof(People.ExternalIDs)) => ProviderRepository + .GetMetadataID(x => x.PeopleID == obj.ID) .Then(x => p.ExternalIDs = x), - (People p, PeopleRole) => (PeopleRepository.GetFromPeople((dynamic)(obj.ID > 0 ? obj.ID : obj.Slug)) + (People p, nameof(People.Roles)) => ( + PeopleRepository.GetFromPeople((dynamic)(obj.ID > 0 ? obj.ID : obj.Slug)) as Task>).Then(x => p.Roles = x), - _ => throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}.") + _ => throw new ArgumentException($"Couldn't find a way to load {member} of {obj.Slug}.") }; } diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index defa3f47..ae13b2f7 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -24,6 +24,16 @@ namespace Kyoo ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression; } + public static string GetPropertyName(LambdaExpression ex) + { + if (!IsPropertyExpression(ex)) + throw new ArgumentException($"{ex} is not a property expression."); + MemberExpression member = ex.Body.NodeType == ExpressionType.Convert + ? ((UnaryExpression)ex.Body).Operand as MemberExpression + : ex.Body as MemberExpression; + return member!.Member.Name; + } + public static string ToSlug(string str) { if (str == null) @@ -32,7 +42,7 @@ namespace Kyoo str = str.ToLowerInvariant(); string normalizedString = str.Normalize(NormalizationForm.FormD); - StringBuilder stringBuilder = new StringBuilder(); + StringBuilder stringBuilder = new(); foreach (char c in normalizedString) { UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); @@ -238,10 +248,12 @@ namespace Kyoo action(); yield break; } - - yield return enumerator.Current; - while (enumerator.MoveNext()) + + do + { yield return enumerator.Current; + } + while (enumerator.MoveNext()); } private static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args) diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index 23d409ec..b5270c4d 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Configuration; namespace Kyoo.CommonApi { [ApiController] + [ResourceView] public class CrudApi : ControllerBase where T : class, IResource { private readonly IRepository _repository; @@ -22,7 +23,8 @@ namespace Kyoo.CommonApi _repository = repository; BaseURL = configuration.GetValue("public_url").TrimEnd('/'); } - + + [HttpGet("{id:int}")] [Authorize(Policy = "Read")] [JsonDetailed] @@ -68,10 +70,6 @@ namespace Kyoo.CommonApi [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _repository.GetAll(ApiHelper.ParseWhere(where), @@ -89,7 +87,7 @@ namespace Kyoo.CommonApi protected Page Page(ICollection resources, int limit) where TResult : IResource { - return new Page(resources, + return new(resources, BaseURL + Request.Path, Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase), limit); diff --git a/Kyoo.CommonAPI/ResourceViewAttribute.cs b/Kyoo.CommonAPI/ResourceViewAttribute.cs new file mode 100644 index 00000000..a30683db --- /dev/null +++ b/Kyoo.CommonAPI/ResourceViewAttribute.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Kyoo.Controllers; +using Kyoo.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; + +namespace Kyoo.CommonApi +{ + public class ResourceViewAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext context) + { + if (context.ActionArguments.TryGetValue("where", out object dic) && dic is Dictionary where) + { + where.Remove("fields"); + foreach ((string key, _) in context.ActionArguments) + where.Remove(key); + } + + context.HttpContext.Items["fields"] = context.HttpContext.Request.Query["fields"].ToArray(); + // TODO Check if fields are loadable properties of the return type. If not, shorfail the request. + base.OnActionExecuting(context); + } + + public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) + { + if (context.Result is ObjectResult result) + await LoadResultRelations(context, result); + await base.OnResultExecutionAsync(context, next); + } + + private static async Task LoadResultRelations(ActionContext context, ObjectResult result) + { + if (result.DeclaredType == null) + return; + + await using ILibraryManager library = context.HttpContext.RequestServices.GetService(); + string[] fields = (string[])context.HttpContext.Items["fields"]; + Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>)); + + + if (pageType != null) + { + foreach (IResource resource in ((Page)result.Value).Items) + { + foreach (string field in fields!) + await library!.Load(resource, field); + } + } + else if (result.DeclaredType.IsAssignableTo(typeof(IResource))) + { + foreach (string field in fields!) + await library!.Load(result.Value as IResource, field); + } + } + } +} \ No newline at end of file diff --git a/Kyoo/Views/API/CollectionApi.cs b/Kyoo/Views/API/CollectionApi.cs index 254be6b6..5bba0650 100644 --- a/Kyoo/Views/API/CollectionApi.cs +++ b/Kyoo/Views/API/CollectionApi.cs @@ -33,10 +33,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetShows( @@ -63,10 +59,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetShows( @@ -93,10 +85,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetLibraries( @@ -123,10 +111,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetLibraries( diff --git a/Kyoo/Views/API/EpisodeApi.cs b/Kyoo/Views/API/EpisodeApi.cs index eff5fe78..d01468b2 100644 --- a/Kyoo/Views/API/EpisodeApi.cs +++ b/Kyoo/Views/API/EpisodeApi.cs @@ -77,9 +77,7 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); + try { @@ -109,9 +107,7 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); + try { @@ -143,9 +139,7 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); + try { diff --git a/Kyoo/Views/API/GenreApi.cs b/Kyoo/Views/API/GenreApi.cs index 2571f347..29393e97 100644 --- a/Kyoo/Views/API/GenreApi.cs +++ b/Kyoo/Views/API/GenreApi.cs @@ -34,10 +34,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetShows( @@ -64,10 +60,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetShows( diff --git a/Kyoo/Views/API/LibraryApi.cs b/Kyoo/Views/API/LibraryApi.cs index c66ae7f1..ba9ad3d0 100644 --- a/Kyoo/Views/API/LibraryApi.cs +++ b/Kyoo/Views/API/LibraryApi.cs @@ -45,10 +45,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetShows( @@ -75,10 +71,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetShows( @@ -105,10 +97,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetCollections( @@ -135,10 +123,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetCollections( @@ -165,10 +149,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetItemsFromLibrary(id, @@ -195,10 +175,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetItemsFromLibrary(slug, diff --git a/Kyoo/Views/API/LibraryItemApi.cs b/Kyoo/Views/API/LibraryItemApi.cs index 19132fa1..e0ae3560 100644 --- a/Kyoo/Views/API/LibraryItemApi.cs +++ b/Kyoo/Views/API/LibraryItemApi.cs @@ -34,10 +34,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryItems.GetAll( diff --git a/Kyoo/Views/API/PeopleApi.cs b/Kyoo/Views/API/PeopleApi.cs index 41d16f04..96d73490 100644 --- a/Kyoo/Views/API/PeopleApi.cs +++ b/Kyoo/Views/API/PeopleApi.cs @@ -36,10 +36,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetRolesFromPeople(id, @@ -69,10 +65,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetRolesFromPeople(slug, diff --git a/Kyoo/Views/API/SeasonApi.cs b/Kyoo/Views/API/SeasonApi.cs index 67206ca1..f6650617 100644 --- a/Kyoo/Views/API/SeasonApi.cs +++ b/Kyoo/Views/API/SeasonApi.cs @@ -33,10 +33,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetEpisodes( @@ -64,10 +60,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetEpisodes( @@ -96,10 +88,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetEpisodes( diff --git a/Kyoo/Views/API/ShowApi.cs b/Kyoo/Views/API/ShowApi.cs index dcb90a66..28b6ef94 100644 --- a/Kyoo/Views/API/ShowApi.cs +++ b/Kyoo/Views/API/ShowApi.cs @@ -37,10 +37,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetSeasons( @@ -67,10 +63,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetSeasons( @@ -97,10 +89,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetEpisodes( @@ -127,10 +115,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetEpisodes( @@ -156,10 +140,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetPeopleFromShow(showID, @@ -185,10 +165,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetPeopleFromShow(slug, @@ -215,10 +191,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetGenres( @@ -245,10 +217,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetGenres( @@ -303,10 +271,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetLibraries( @@ -333,10 +297,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetLibraries( @@ -363,10 +323,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetCollections( @@ -393,10 +349,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetCollections( diff --git a/Kyoo/Views/API/StudioApi.cs b/Kyoo/Views/API/StudioApi.cs index 96c91463..e244575d 100644 --- a/Kyoo/Views/API/StudioApi.cs +++ b/Kyoo/Views/API/StudioApi.cs @@ -34,10 +34,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetShows( @@ -64,10 +60,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - try { ICollection resources = await _libraryManager.GetShows( From faf453e64a31c94f8dc314eabe264a752691b80d Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 27 Feb 2021 23:34:28 +0100 Subject: [PATCH 15/54] Adding a field property with auto loading & removing non-loaded fields from the json serialization. Reworking all json serialization --- Kyoo.Common/Controllers/ILibraryManager.cs | 4 +- Kyoo.Common/Models/Attributes/ComposedSlug.cs | 2 +- .../Models/Attributes/EditableRelation.cs | 7 -- .../Models/Attributes/MergeAttributes.cs | 3 - .../Models/Attributes/RelationAttributes.cs | 10 ++ .../Models/Attributes/SerializeAttribute.cs | 10 ++ Kyoo.Common/Models/MetadataID.cs | 20 ++-- Kyoo.Common/Models/PeopleRole.cs | 12 +-- Kyoo.Common/Models/Resources/Collection.cs | 4 +- Kyoo.Common/Models/Resources/Episode.cs | 26 ++--- Kyoo.Common/Models/Resources/Genre.cs | 4 +- Kyoo.Common/Models/Resources/Library.cs | 6 +- Kyoo.Common/Models/Resources/People.cs | 4 +- Kyoo.Common/Models/Resources/ProviderID.cs | 2 +- Kyoo.Common/Models/Resources/Season.cs | 13 +-- Kyoo.Common/Models/Resources/Show.cs | 20 ++-- Kyoo.Common/Models/Resources/Studio.cs | 4 +- Kyoo.Common/Models/Resources/Track.cs | 12 +-- Kyoo.Common/Models/WatchItem.cs | 5 +- Kyoo.CommonAPI/CrudApi.cs | 2 - Kyoo.CommonAPI/JsonSerializer.cs | 94 ++----------------- Kyoo.CommonAPI/LocalRepository.cs | 17 +--- Kyoo.CommonAPI/ResourceViewAttribute.cs | 34 ++++++- Kyoo/Models/Resources/CollectionDE.cs | 4 +- Kyoo/Models/Resources/GenreDE.cs | 4 +- Kyoo/Models/Resources/LibraryDE.cs | 4 +- Kyoo/Models/Resources/ShowDE.cs | 6 +- Kyoo/Views/API/EpisodeApi.cs | 22 +++-- Kyoo/Views/API/PeopleApi.cs | 14 ++- Kyoo/Views/API/SeasonApi.cs | 32 +++++++ 30 files changed, 191 insertions(+), 210 deletions(-) delete mode 100644 Kyoo.Common/Models/Attributes/EditableRelation.cs create mode 100644 Kyoo.Common/Models/Attributes/RelationAttributes.cs create mode 100644 Kyoo.Common/Models/Attributes/SerializeAttribute.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index f3f0910a..e6972d04 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -69,10 +69,10 @@ namespace Kyoo.Controllers where T : class, IResource where T2 : class, new(); - Task Load(T obj, string memberName) + Task Load([NotNull] T obj, string memberName) where T : class, IResource; - Task Load(IResource obj, string memberName); + Task Load([NotNull] IResource obj, string memberName); // Library Items relations Task> GetItemsFromLibrary(int id, diff --git a/Kyoo.Common/Models/Attributes/ComposedSlug.cs b/Kyoo.Common/Models/Attributes/ComposedSlug.cs index a66fef71..4595d5a4 100644 --- a/Kyoo.Common/Models/Attributes/ComposedSlug.cs +++ b/Kyoo.Common/Models/Attributes/ComposedSlug.cs @@ -3,5 +3,5 @@ using System; namespace Kyoo.Models.Attributes { [AttributeUsage(AttributeTargets.Class)] - public class ComposedSlug : Attribute { } + public class ComposedSlugAttribute : Attribute { } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Attributes/EditableRelation.cs b/Kyoo.Common/Models/Attributes/EditableRelation.cs deleted file mode 100644 index 4275dca4..00000000 --- a/Kyoo.Common/Models/Attributes/EditableRelation.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace Kyoo.Models.Attributes -{ - [AttributeUsage(AttributeTargets.Property, Inherited = false)] - public class EditableRelation : Attribute { } -} \ No newline at end of file diff --git a/Kyoo.Common/Models/Attributes/MergeAttributes.cs b/Kyoo.Common/Models/Attributes/MergeAttributes.cs index 1944c89b..399f5389 100644 --- a/Kyoo.Common/Models/Attributes/MergeAttributes.cs +++ b/Kyoo.Common/Models/Attributes/MergeAttributes.cs @@ -8,7 +8,4 @@ namespace Kyoo.Models.Attributes { void OnMerge(object merged); } - - public class JsonReadOnly : Attribute { } - public class JsonIgnore : JsonReadOnly { } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Attributes/RelationAttributes.cs b/Kyoo.Common/Models/Attributes/RelationAttributes.cs new file mode 100644 index 00000000..36edbbf2 --- /dev/null +++ b/Kyoo.Common/Models/Attributes/RelationAttributes.cs @@ -0,0 +1,10 @@ +using System; + +namespace Kyoo.Models.Attributes +{ + [AttributeUsage(AttributeTargets.Property, Inherited = false)] + public class EditableRelationAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + public class LoadableRelationAttribute : Attribute { } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs new file mode 100644 index 00000000..3622f77d --- /dev/null +++ b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Kyoo.Models.Attributes +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class SerializeIgnoreAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class DeserializeIgnoreAttribute : Attribute {} +} \ No newline at end of file diff --git a/Kyoo.Common/Models/MetadataID.cs b/Kyoo.Common/Models/MetadataID.cs index 945e578b..71e1946a 100644 --- a/Kyoo.Common/Models/MetadataID.cs +++ b/Kyoo.Common/Models/MetadataID.cs @@ -4,21 +4,21 @@ namespace Kyoo.Models { public class MetadataID { - [JsonIgnore] public int ID { get; set; } - [JsonIgnore] public int ProviderID { get; set; } + [SerializeIgnore] public int ID { get; set; } + [SerializeIgnore] public int ProviderID { get; set; } public virtual ProviderID Provider {get; set; } - [JsonIgnore] public int? ShowID { get; set; } - [JsonIgnore] public virtual Show Show { get; set; } + [SerializeIgnore] public int? ShowID { get; set; } + [SerializeIgnore] public virtual Show Show { get; set; } - [JsonIgnore] public int? EpisodeID { get; set; } - [JsonIgnore] public virtual Episode Episode { get; set; } + [SerializeIgnore] public int? EpisodeID { get; set; } + [SerializeIgnore] public virtual Episode Episode { get; set; } - [JsonIgnore] public int? SeasonID { get; set; } - [JsonIgnore] public virtual Season Season { get; set; } + [SerializeIgnore] public int? SeasonID { get; set; } + [SerializeIgnore] public virtual Season Season { get; set; } - [JsonIgnore] public int? PeopleID { get; set; } - [JsonIgnore] public virtual People People { get; set; } + [SerializeIgnore] public int? PeopleID { get; set; } + [SerializeIgnore] public virtual People People { get; set; } public string DataID { get; set; } public string Link { get; set; } diff --git a/Kyoo.Common/Models/PeopleRole.cs b/Kyoo.Common/Models/PeopleRole.cs index a9fc0582..fbde0d5e 100644 --- a/Kyoo.Common/Models/PeopleRole.cs +++ b/Kyoo.Common/Models/PeopleRole.cs @@ -8,9 +8,9 @@ namespace Kyoo.Models { public class PeopleRole : IResource { - [JsonIgnore] public int ID { get; set; } - [JsonIgnore] public int PeopleID { get; set; } - [JsonIgnore] public virtual People People { get; set; } + [SerializeIgnore] public int ID { get; set; } + [SerializeIgnore] public int PeopleID { get; set; } + [SerializeIgnore] public virtual People People { get; set; } [ExpressionRewrite(nameof(People) + "." + nameof(Models.People.Slug))] public string Slug @@ -40,8 +40,8 @@ namespace Kyoo.Models set => People.ExternalIDs = value; } - [JsonIgnore] public int ShowID { get; set; } - [JsonIgnore] public virtual Show Show { get; set; } + [SerializeIgnore] public int ShowID { get; set; } + [SerializeIgnore] public virtual Show Show { get; set; } public string Role { get; set; } public string Type { get; set; } @@ -77,7 +77,7 @@ namespace Kyoo.Models public string Slug { get; set; } public string Title { get; set; } public ICollection Aliases { get; set; } - [JsonIgnore] public string Path { get; set; } + [SerializeIgnore] public string Path { get; set; } public string Overview { get; set; } public Status? Status { get; set; } public string TrailerUrl { get; set; } diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 283fe017..34a41f7a 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -10,8 +10,8 @@ namespace Kyoo.Models public string Name { get; set; } public string Poster { get; set; } public string Overview { get; set; } - [JsonIgnore] public virtual ICollection Shows { get; set; } - [JsonIgnore] public virtual ICollection Libraries { get; set; } + [LoadableRelation] public virtual ICollection Shows { get; set; } + [LoadableRelation] public virtual ICollection Libraries { get; set; } public Collection() { } diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 7a8007ee..9ef8c684 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -9,37 +9,29 @@ namespace Kyoo.Models public class Episode : IResource, IOnMerge { public int ID { get; set; } + public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString(); public int ShowID { get; set; } - [JsonIgnore] public virtual Show Show { get; set; } + [LoadableRelation] public virtual Show Show { get; set; } public int? SeasonID { get; set; } - [JsonIgnore] public virtual Season Season { get; set; } + [LoadableRelation] public virtual Season Season { get; set; } public int SeasonNumber { get; set; } = -1; public int EpisodeNumber { get; set; } = -1; public int AbsoluteNumber { get; set; } = -1; - [JsonIgnore] public string Path { get; set; } + [SerializeIgnore] public string Path { get; set; } + public string Thumb => $"/api/episodes/{Slug}/thumb"; public string Title { get; set; } public string Overview { get; set; } public DateTime? ReleaseDate { get; set; } public int Runtime { get; set; } //This runtime variable should be in minutes - [JsonIgnore] public string Poster { get; set; } - [EditableRelation] public virtual ICollection ExternalIDs { get; set; } + [SerializeIgnore] public string Poster { get; set; } + [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [JsonIgnore] public virtual ICollection Tracks { get; set; } + [LoadableRelation] public virtual ICollection Tracks { get; set; } - public string ShowTitle => Show.Title; - public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString(); - public string Thumb - { - get - { - if (Show != null) - return "thumb/" + Slug; - return Poster; - } - } + public string ShowTitle => Show?.Title; public Episode() { } diff --git a/Kyoo.Common/Models/Resources/Genre.cs b/Kyoo.Common/Models/Resources/Genre.cs index 16e8640d..26fbe5a4 100644 --- a/Kyoo.Common/Models/Resources/Genre.cs +++ b/Kyoo.Common/Models/Resources/Genre.cs @@ -5,11 +5,11 @@ namespace Kyoo.Models { public class Genre : IResource { - [JsonIgnore] public int ID { get; set; } + public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } - [JsonIgnore] public virtual ICollection Shows { get; set; } + [LoadableRelation] public virtual ICollection Shows { get; set; } public Genre() {} diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index 59e5b01d..1556753b 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -6,15 +6,15 @@ namespace Kyoo.Models { public class Library : IResource { - [JsonIgnore] public int ID { get; set; } + public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } public string[] Paths { get; set; } [EditableRelation] public virtual ICollection Providers { get; set; } - [JsonIgnore] public virtual ICollection Shows { get; set; } - [JsonIgnore] public virtual ICollection Collections { get; set; } + [LoadableRelation] public virtual ICollection Shows { get; set; } + [LoadableRelation] public virtual ICollection Collections { get; set; } public Library() { } diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index fe9cc4a1..ca1aec5d 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -10,9 +10,9 @@ namespace Kyoo.Models public string Slug { get; set; } public string Name { get; set; } public string Poster { get; set; } - [EditableRelation] public virtual ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [EditableRelation] [JsonReadOnly] public virtual ICollection Roles { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection Roles { get; set; } public People() {} diff --git a/Kyoo.Common/Models/Resources/ProviderID.cs b/Kyoo.Common/Models/Resources/ProviderID.cs index 49e19774..411bf976 100644 --- a/Kyoo.Common/Models/Resources/ProviderID.cs +++ b/Kyoo.Common/Models/Resources/ProviderID.cs @@ -4,7 +4,7 @@ namespace Kyoo.Models { public class ProviderID : IResource { - [JsonIgnore] public int ID { get; set; } + public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } public string Logo { get; set; } diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 0aee3e9a..ecaff2e4 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -7,8 +7,8 @@ namespace Kyoo.Models [ComposedSlug] public class Season : IResource { - [JsonIgnore] public int ID { get; set; } - [JsonIgnore] public int ShowID { get; set; } + public int ID { get; set; } + public int ShowID { get; set; } public int SeasonNumber { get; set; } = -1; @@ -17,11 +17,12 @@ namespace Kyoo.Models public string Overview { get; set; } public int? Year { get; set; } - [JsonIgnore] public string Poster { get; set; } - [EditableRelation] public virtual ICollection ExternalIDs { get; set; } + [SerializeIgnore] public string Poster { get; set; } + public string Thumb => $"/api/seasons/{Slug}/thumb"; + [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [JsonIgnore] public virtual Show Show { get; set; } - [JsonIgnore] public virtual ICollection Episodes { get; set; } + [LoadableRelation] public virtual Show Show { get; set; } + [LoadableRelation] public virtual ICollection Episodes { get; set; } public Season() { } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index fadac0bd..c4feb24c 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models public string Slug { get; set; } public string Title { get; set; } [EditableRelation] public string[] Aliases { get; set; } - [JsonIgnore] public string Path { get; set; } + [SerializeIgnore] public string Path { get; set; } public string Overview { get; set; } public Status? Status { get; set; } public string TrailerUrl { get; set; } @@ -24,17 +24,17 @@ namespace Kyoo.Models public bool IsMovie { get; set; } - [EditableRelation] public virtual ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [JsonIgnore] public int? StudioID { get; set; } - [EditableRelation] [JsonReadOnly] public virtual Studio Studio { get; set; } - [EditableRelation] [JsonReadOnly] public virtual ICollection Genres { get; set; } - [EditableRelation] [JsonReadOnly] public virtual ICollection People { get; set; } - [JsonIgnore] public virtual ICollection Seasons { get; set; } - [JsonIgnore] public virtual ICollection Episodes { get; set; } - [JsonIgnore] public virtual ICollection Libraries { get; set; } - [JsonIgnore] public virtual ICollection Collections { get; set; } + public int? StudioID { get; set; } + [LoadableRelation] [EditableRelation] public virtual Studio Studio { get; set; } + [LoadableRelation] [EditableRelation] public virtual ICollection Genres { get; set; } + [LoadableRelation] [EditableRelation] public virtual ICollection People { get; set; } + [LoadableRelation] public virtual ICollection Seasons { get; set; } + [LoadableRelation] public virtual ICollection Episodes { get; set; } + [LoadableRelation] public virtual ICollection Libraries { get; set; } + [LoadableRelation] public virtual ICollection Collections { get; set; } public Show() { } diff --git a/Kyoo.Common/Models/Resources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs index 07959b01..9eea3a7b 100644 --- a/Kyoo.Common/Models/Resources/Studio.cs +++ b/Kyoo.Common/Models/Resources/Studio.cs @@ -5,11 +5,11 @@ namespace Kyoo.Models { public class Studio : IResource { - [JsonIgnore] public int ID { get; set; } + public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } - [JsonIgnore] public virtual ICollection Shows { get; set; } + [LoadableRelation] public virtual ICollection Shows { get; set; } public Studio() { } diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 86e4bdbb..f4b82fc7 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -25,8 +25,8 @@ namespace Kyoo.Models public string Codec { get; set; } [MarshalAs(UnmanagedType.I1)] public bool isDefault; [MarshalAs(UnmanagedType.I1)] public bool isForced; - [JsonIgnore] public string Path { get; set; } - [JsonIgnore] public StreamType Type { get; set; } + [SerializeIgnore] public string Path { get; set; } + [SerializeIgnore] public StreamType Type { get; set; } public Stream() {} @@ -56,8 +56,8 @@ namespace Kyoo.Models public class Track : Stream, IResource { - [JsonIgnore] public int ID { get; set; } - [JsonIgnore] public int EpisodeID { get; set; } + public int ID { get; set; } + public int EpisodeID { get; set; } public bool IsDefault { get => isDefault; @@ -113,8 +113,8 @@ namespace Kyoo.Models } } - [JsonIgnore] public bool IsExternal { get; set; } - [JsonIgnore] public virtual Episode Episode { get; set; } + public bool IsExternal { get; set; } + [LoadableRelation] public virtual Episode Episode { get; set; } public Track() { } diff --git a/Kyoo.Common/Models/WatchItem.cs b/Kyoo.Common/Models/WatchItem.cs index a86f5490..e007e04c 100644 --- a/Kyoo.Common/Models/WatchItem.cs +++ b/Kyoo.Common/Models/WatchItem.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models.Attributes; -using Kyoo.Models.Watch; using PathIO = System.IO.Path; namespace Kyoo.Models @@ -26,7 +25,7 @@ namespace Kyoo.Models public class WatchItem { - [JsonIgnore] public readonly int EpisodeID = -1; + public readonly int EpisodeID = -1; public string ShowTitle; public string ShowSlug; @@ -35,7 +34,7 @@ namespace Kyoo.Models public string Title; public string Slug; public DateTime? ReleaseDate; - [JsonIgnore] public string Path; + [SerializeIgnore] public string Path; public Episode PreviousEpisode; public Episode NextEpisode; public bool IsMovie; diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index b5270c4d..4d674cc2 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -27,7 +27,6 @@ namespace Kyoo.CommonApi [HttpGet("{id:int}")] [Authorize(Policy = "Read")] - [JsonDetailed] public virtual async Task> Get(int id) { T resource = await _repository.Get(id); @@ -39,7 +38,6 @@ namespace Kyoo.CommonApi [HttpGet("{slug}")] [Authorize(Policy = "Read")] - [JsonDetailed] public virtual async Task> Get(string slug) { T resource = await _repository.Get(slug); diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 6cbc9c89..78f49ed8 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -1,13 +1,5 @@ -using System; -using System.Buffers; -using System.Collections.Generic; using System.Reflection; -using Kyoo.Models; using Kyoo.Models.Attributes; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -19,85 +11,15 @@ namespace Kyoo.Controllers { JsonProperty property = base.CreateProperty(member, memberSerialization); - property.ShouldSerialize = i => member.GetCustomAttribute(true) == null; - property.ShouldDeserialize = i => member.GetCustomAttribute(true) == null; + // TODO this get called only once and get cached. + + // if (member?.GetCustomAttributes() != null) + // property.NullValueHandling = NullValueHandling.Ignore; + // if (member?.GetCustomAttributes() != null) + // property.ShouldSerialize = _ => false; + // if (member?.GetCustomAttributes() != null) + // property.ShouldDeserialize = _ => false; return property; } } - - public class JsonPropertySelector : JsonPropertyIgnorer - { - private readonly Dictionary> _ignored; - private readonly Dictionary> _forceSerialize; - - public JsonPropertySelector() - { - _ignored = new Dictionary>(); - _forceSerialize = new Dictionary>(); - } - - public JsonPropertySelector(Dictionary> ignored, - Dictionary> forceSerialize = null) - { - _ignored = ignored ?? new Dictionary>(); - _forceSerialize = forceSerialize ?? new Dictionary>(); - } - - private bool IsIgnored(Type type, string propertyName) - { - while (type != null) - { - if (_ignored.ContainsKey(type) && _ignored[type].Contains(propertyName)) - return true; - type = type.BaseType; - } - - return false; - } - - private bool IsSerializationForced(Type type, string propertyName) - { - while (type != null) - { - if (_forceSerialize.ContainsKey(type) && _forceSerialize[type].Contains(propertyName)) - return true; - type = type.BaseType; - } - - return false; - } - - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - JsonProperty property = base.CreateProperty(member, memberSerialization); - - if (IsSerializationForced(property.DeclaringType, property.PropertyName)) - property.ShouldSerialize = i => true; - else if (IsIgnored(property.DeclaringType, property.PropertyName)) - property.ShouldSerialize = i => false; - return property; - } - } - - public class JsonDetailed : ActionFilterAttribute - { - public override void OnActionExecuted(ActionExecutedContext context) - { - if (context.Result is ObjectResult result) - { - result.Formatters.Add(new NewtonsoftJsonOutputFormatter( - new JsonSerializerSettings - { - ContractResolver = new JsonPropertySelector(null, new Dictionary> - { - {typeof(Show), new HashSet {"genres", "studio"}}, - {typeof(Episode), new HashSet {"tracks"}}, - {typeof(PeopleRole), new HashSet {"show"}} - }) - }, - context.HttpContext.RequestServices.GetRequiredService>(), - new MvcOptions())); - } - } - } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index d4197e08..3f2b4cf6 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -164,7 +163,7 @@ namespace Kyoo.Controllers foreach (NavigationEntry navigation in Database.Entry(old).Navigations) { - if (navigation.Metadata.PropertyInfo.GetCustomAttribute() != null) + if (navigation.Metadata.PropertyInfo.GetCustomAttribute() != null) { if (resetOld) { @@ -207,7 +206,7 @@ namespace Kyoo.Controllers { if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("Resource can't have null as a slug."); - if (int.TryParse(resource.Slug, out int _) && typeof(T).GetCustomAttribute() == null) + if (int.TryParse(resource.Slug, out int _) && typeof(T).GetCustomAttribute() == null) { try { @@ -222,18 +221,6 @@ namespace Kyoo.Controllers throw new ArgumentException("Resources slug can't be number only."); } } - - foreach (PropertyInfo property in typeof(T).GetProperties() - .Where(x => typeof(IEnumerable).IsAssignableFrom(x.PropertyType) - && !typeof(string).IsAssignableFrom(x.PropertyType) - && x.GetCustomAttribute() != null)) - { - object value = property.GetValue(resource); - if (value == null || value is ICollection || Utility.IsOfGenericType(value, typeof(ICollection<>))) - continue; - value = Utility.RunGenericMethod(typeof(Enumerable), "ToList", Utility.GetEnumerableType((IEnumerable)value), value); - property.SetValue(resource, value); - } return Task.CompletedTask; } diff --git a/Kyoo.CommonAPI/ResourceViewAttribute.cs b/Kyoo.CommonAPI/ResourceViewAttribute.cs index a30683db..9a2dab33 100644 --- a/Kyoo.CommonAPI/ResourceViewAttribute.cs +++ b/Kyoo.CommonAPI/ResourceViewAttribute.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; +using Kyoo.Models.Attributes; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; @@ -20,8 +24,30 @@ namespace Kyoo.CommonApi where.Remove(key); } - context.HttpContext.Items["fields"] = context.HttpContext.Request.Query["fields"].ToArray(); - // TODO Check if fields are loadable properties of the return type. If not, shorfail the request. + string[] fields = context.HttpContext.Request.Query["fields"].ToArray(); + + context.HttpContext.Items["fields"] = fields; + if (context.ActionDescriptor is ControllerActionDescriptor descriptor) + { + Type type = descriptor.MethodInfo.ReturnType; + type = Utility.GetGenericDefinition(type, typeof(Task<>))?.GetGenericArguments()[0] ?? type; + type = Utility.GetGenericDefinition(type, typeof(ActionResult<>))?.GetGenericArguments()[0] ?? type; + type = Utility.GetGenericDefinition(type, typeof(Page<>))?.GetGenericArguments()[0] ?? type; + + PropertyInfo[] properties = type.GetProperties() + .Where(x => x.GetCustomAttribute() != null) + .ToArray(); + foreach (string field in fields) + { + if (properties.Any(y => string.Equals(y.Name,field, StringComparison.InvariantCultureIgnoreCase))) + continue; + context.Result = new BadRequestObjectResult(new + { + Error = $"{field} does not exist on {type.Name}." + }); + return; + } + } base.OnActionExecuting(context); } @@ -44,7 +70,7 @@ namespace Kyoo.CommonApi if (pageType != null) { - foreach (IResource resource in ((Page)result.Value).Items) + foreach (IResource resource in ((dynamic)result.Value).Items) { foreach (string field in fields!) await library!.Load(resource, field); @@ -53,7 +79,7 @@ namespace Kyoo.CommonApi else if (result.DeclaredType.IsAssignableTo(typeof(IResource))) { foreach (string field in fields!) - await library!.Load(result.Value as IResource, field); + await library!.Load((IResource)result.Value, field); } } } diff --git a/Kyoo/Models/Resources/CollectionDE.cs b/Kyoo/Models/Resources/CollectionDE.cs index 622e602e..10a53852 100644 --- a/Kyoo/Models/Resources/CollectionDE.cs +++ b/Kyoo/Models/Resources/CollectionDE.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models { public class CollectionDE : Collection { - [JsonIgnore] [NotMergable] public virtual ICollection Links { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection Links { get; set; } [ExpressionRewrite(nameof(Links), nameof(CollectionLink.Child))] public override ICollection Shows { @@ -14,7 +14,7 @@ namespace Kyoo.Models set => Links = value?.Select(x => new CollectionLink(this, x)).ToList(); } - [JsonIgnore] [NotMergable] public virtual ICollection LibraryLinks { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection LibraryLinks { get; set; } [ExpressionRewrite(nameof(LibraryLinks), nameof(GenreLink.Child))] public override ICollection Libraries diff --git a/Kyoo/Models/Resources/GenreDE.cs b/Kyoo/Models/Resources/GenreDE.cs index 892be000..0a77198e 100644 --- a/Kyoo/Models/Resources/GenreDE.cs +++ b/Kyoo/Models/Resources/GenreDE.cs @@ -6,10 +6,10 @@ namespace Kyoo.Models { public class GenreDE : Genre { - [JsonIgnore] [NotMergable] public virtual ICollection Links { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection Links { get; set; } [ExpressionRewrite(nameof(Links), nameof(GenreLink.Child))] - [JsonIgnore] [NotMergable] public override ICollection Shows + [SerializeIgnore] [NotMergable] public override ICollection Shows { get => Links?.Select(x => x.Parent).ToList(); set => Links = value?.Select(x => new GenreLink(x, this)).ToList(); diff --git a/Kyoo/Models/Resources/LibraryDE.cs b/Kyoo/Models/Resources/LibraryDE.cs index 32992c6c..47c94c18 100644 --- a/Kyoo/Models/Resources/LibraryDE.cs +++ b/Kyoo/Models/Resources/LibraryDE.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models { public class LibraryDE : Library { - [EditableRelation] [JsonIgnore] [NotMergable] public virtual ICollection ProviderLinks { get; set; } + [EditableRelation] [SerializeIgnore] [NotMergable] public virtual ICollection ProviderLinks { get; set; } [ExpressionRewrite(nameof(ProviderLinks), nameof(ProviderLink.Child))] public override ICollection Providers { @@ -14,7 +14,7 @@ namespace Kyoo.Models set => ProviderLinks = value?.Select(x => new ProviderLink(x, this)).ToList(); } - [JsonIgnore] [NotMergable] public virtual ICollection Links { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection Links { get; set; } [ExpressionRewrite(nameof(Links), nameof(LibraryLink.Show))] public override ICollection Shows { diff --git a/Kyoo/Models/Resources/ShowDE.cs b/Kyoo/Models/Resources/ShowDE.cs index e1928214..7491effe 100644 --- a/Kyoo/Models/Resources/ShowDE.cs +++ b/Kyoo/Models/Resources/ShowDE.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models { public class ShowDE : Show { - [EditableRelation] [JsonReadOnly] [NotMergable] public virtual ICollection GenreLinks { get; set; } + [EditableRelation] [SerializeIgnore] [NotMergable] public virtual ICollection GenreLinks { get; set; } [ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Child))] public override ICollection Genres { @@ -14,7 +14,7 @@ namespace Kyoo.Models set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList(); } - [JsonReadOnly] [NotMergable] public virtual ICollection LibraryLinks { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection LibraryLinks { get; set; } [ExpressionRewrite(nameof(LibraryLinks), nameof(LibraryLink.Library))] public override ICollection Libraries { @@ -22,7 +22,7 @@ namespace Kyoo.Models set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)).ToList(); } - [JsonReadOnly] [NotMergable] public virtual ICollection CollectionLinks { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection CollectionLinks { get; set; } [ExpressionRewrite(nameof(CollectionLinks), nameof(CollectionLink.Parent))] public override ICollection Collections { diff --git a/Kyoo/Views/API/EpisodeApi.cs b/Kyoo/Views/API/EpisodeApi.cs index d01468b2..de9ae424 100644 --- a/Kyoo/Views/API/EpisodeApi.cs +++ b/Kyoo/Views/API/EpisodeApi.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; -using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; @@ -77,8 +76,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - - try { ICollection resources = await _libraryManager.GetTracks( @@ -107,8 +104,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - - try { ICollection resources = await _libraryManager.GetTracks( @@ -139,8 +134,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - - try { ICollection resources = await _libraryManager.GetTracks(ApiHelper.ParseWhere(where, x => x.Episode.Show.Slug == showSlug @@ -159,6 +152,21 @@ namespace Kyoo.Api } } + [HttpGet("{id:int}/thumb")] + [Authorize(Policy="Read")] + public async Task GetThumb(int id) + { + string path = (await _libraryManager.GetEpisode(id))?.Path; + if (path == null) + return NotFound(); + + string thumb = Path.ChangeExtension(path, "jpg"); + + if (System.IO.File.Exists(thumb)) + return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); + return NotFound(); + } + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/thumb")] [Authorize(Policy="Read")] public async Task GetThumb(string showSlug, int seasonNumber, int episodeNumber) diff --git a/Kyoo/Views/API/PeopleApi.cs b/Kyoo/Views/API/PeopleApi.cs index 96d73490..a264363c 100644 --- a/Kyoo/Views/API/PeopleApi.cs +++ b/Kyoo/Views/API/PeopleApi.cs @@ -29,7 +29,6 @@ namespace Kyoo.Api [HttpGet("{id:int}/role")] [HttpGet("{id:int}/roles")] [Authorize(Policy = "Read")] - [JsonDetailed] public async Task>> GetRoles(int id, [FromQuery] string sortBy, [FromQuery] int afterID, @@ -58,7 +57,6 @@ namespace Kyoo.Api [HttpGet("{slug}/role")] [HttpGet("{slug}/roles")] [Authorize(Policy = "Read")] - [JsonDetailed] public async Task>> GetRoles(string slug, [FromQuery] string sortBy, [FromQuery] int afterID, @@ -84,12 +82,20 @@ namespace Kyoo.Api } } + [HttpGet("{id:int}/poster")] + [Authorize(Policy="Read")] + public async Task GetPeopleIcon(int id) + { + string slug = (await _libraryManager.GetPeople(id)).Slug; + return GetPeopleIcon(slug); + } + [HttpGet("{slug}/poster")] [Authorize(Policy="Read")] public IActionResult GetPeopleIcon(string slug) { - string thumbPath = Path.Combine(_peoplePath, slug + ".jpg"); - if (!System.IO.File.Exists(thumbPath)) + string thumbPath = Path.GetFullPath(Path.Combine(_peoplePath, slug + ".jpg")); + if (!thumbPath.StartsWith(_peoplePath) || !System.IO.File.Exists(thumbPath)) return NotFound(); return new PhysicalFileResult(Path.GetFullPath(thumbPath), "image/jpg"); diff --git a/Kyoo/Views/API/SeasonApi.cs b/Kyoo/Views/API/SeasonApi.cs index f6650617..4bedf5ec 100644 --- a/Kyoo/Views/API/SeasonApi.cs +++ b/Kyoo/Views/API/SeasonApi.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; @@ -125,5 +126,36 @@ namespace Kyoo.Api { return await _libraryManager.GetShow(showID); } + + [HttpGet("{id:int}/thumb")] + [Authorize(Policy="Read")] + public async Task GetThumb(int id) + { + // TODO remove the next lambda and use a Season.Path (should exit for seasons in a different folder) + string path = (await _libraryManager.GetShow(x => x.Seasons.Any(y => y.ID == id)))?.Path; + int seasonNumber = (await _libraryManager.GetSeason(id)).SeasonNumber; + if (path == null) + return NotFound(); + + string thumb = Path.Combine(path, $"season-{seasonNumber}.jpg"); + if (System.IO.File.Exists(thumb)) + return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); + return NotFound(); + } + + [HttpGet("{showSlug}-s{seasonNumber:int}/thumb")] + [Authorize(Policy="Read")] + public async Task GetThumb(string showSlug, int seasonNumber) + { + // TODO use a season.Path + string path = (await _libraryManager.GetShow(showSlug))?.Path; + if (path == null) + return NotFound(); + + string thumb = Path.Combine(path, $"season-{seasonNumber}.jpg"); + if (System.IO.File.Exists(thumb)) + return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); + return NotFound(); + } } } \ No newline at end of file From 7e8ab3b17351fb34797b74016bf2a35a9edc7185 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 28 Feb 2021 05:15:44 +0100 Subject: [PATCH 16/54] Fixing the json serializer --- .../Controllers/Implementations/LibraryManager.cs | 3 ++- Kyoo.CommonAPI/JsonSerializer.cs | 14 ++++++-------- Kyoo.CommonAPI/ResourceViewAttribute.cs | 3 ++- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index ad1554a6..640ed956 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -302,8 +302,9 @@ namespace Kyoo.Controllers (Show s, nameof(Show.Collections)) => CollectionRepository .GetAll(Utility.ResourceEquals(obj)) .Then(x => s.Collections = x), + // TODO Studio loading does not work. (Show s, nameof(Show.Studio)) => StudioRepository - .Get(x => x.Shows.Any(Utility.ResourceEqualsFunc(obj))) + .Get(x => x.Shows.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => s.Studio = x), (Season s, nameof(Season.ExternalIDs)) => ProviderRepository diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 78f49ed8..d0d30975 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -11,14 +11,12 @@ namespace Kyoo.Controllers { JsonProperty property = base.CreateProperty(member, memberSerialization); - // TODO this get called only once and get cached. - - // if (member?.GetCustomAttributes() != null) - // property.NullValueHandling = NullValueHandling.Ignore; - // if (member?.GetCustomAttributes() != null) - // property.ShouldSerialize = _ => false; - // if (member?.GetCustomAttributes() != null) - // property.ShouldDeserialize = _ => false; + if (member?.GetCustomAttribute() != null) + property.NullValueHandling = NullValueHandling.Ignore; + if (member?.GetCustomAttribute() != null) + property.ShouldSerialize = _ => false; + if (member?.GetCustomAttribute() != null) + property.ShouldDeserialize = _ => false; return property; } } diff --git a/Kyoo.CommonAPI/ResourceViewAttribute.cs b/Kyoo.CommonAPI/ResourceViewAttribute.cs index 9a2dab33..565c1e4b 100644 --- a/Kyoo.CommonAPI/ResourceViewAttribute.cs +++ b/Kyoo.CommonAPI/ResourceViewAttribute.cs @@ -24,7 +24,7 @@ namespace Kyoo.CommonApi where.Remove(key); } - string[] fields = context.HttpContext.Request.Query["fields"].ToArray(); + string[] fields = string.Join(',', context.HttpContext.Request.Query["fields"]).Split(','); context.HttpContext.Items["fields"] = fields; if (context.ActionDescriptor is ControllerActionDescriptor descriptor) @@ -68,6 +68,7 @@ namespace Kyoo.CommonApi Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>)); + // TODO loading is case sensitive. Maybe convert them in the first check. if (pageType != null) { foreach (IResource resource in ((dynamic)result.Value).Items) From 00bf834a29a493f025341e975f33f7b80321bb7d Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 28 Feb 2021 22:30:55 +0100 Subject: [PATCH 17/54] Fixing the load --- .../Implementations/LibraryManager.cs | 46 +++++++++++-------- Kyoo.Common/Models/Resources/Library.cs | 2 +- Kyoo.Common/Models/Resources/ProviderID.cs | 3 ++ Kyoo.CommonAPI/ResourceViewAttribute.cs | 29 +++++++----- ....cs => 20210228210101_Initial.Designer.cs} | 4 +- ...8_Initial.cs => 20210228210101_Initial.cs} | 2 +- .../Internal/DatabaseContextModelSnapshot.cs | 2 +- 7 files changed, 52 insertions(+), 36 deletions(-) rename Kyoo/Models/DatabaseMigrations/Internal/{20210216202218_Initial.Designer.cs => 20210228210101_Initial.Designer.cs} (99%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210216202218_Initial.cs => 20210228210101_Initial.cs} (99%) diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 640ed956..306e9bd7 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -260,47 +260,49 @@ namespace Kyoo.Controllers await Load(obj as IResource, member); return obj; } - + public Task Load(IResource obj, string member) { return (obj, member) switch { (Library l, nameof(Library.Providers)) => ProviderRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => l.Providers = x), - (Library l, nameof(Library.Shows)) => ShowRepository.GetAll(Utility.ResourceEquals(obj)) + (Library l, nameof(Library.Shows)) => ShowRepository + .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => l.Shows = x), (Library l, nameof(Library.Collections)) => CollectionRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => l.Collections = x), (Collection c, nameof(Library.Shows)) => ShowRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Collections.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => c.Shows = x), (Collection c, nameof(Collection.Libraries)) => LibraryRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Collections.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => c.Libraries = x), (Show s, nameof(Show.ExternalIDs)) => ProviderRepository .GetMetadataID(x => x.ShowID == obj.ID) .Then(x => s.ExternalIDs = x), (Show s, nameof(Show.Genres)) => GenreRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Shows.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => s.Genres = x), (Show s, nameof(Show.People)) => ( PeopleRepository.GetFromShow((dynamic)(obj.ID > 0 ? obj.ID : obj.Slug)) - as Task>).Then(x => s.People = x), + as Task>) + .Then(x => s.People = x), (Show s, nameof(Show.Seasons)) => SeasonRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Show.ID == obj.ID || x.Show.Slug == obj.Slug) .Then(x => s.Seasons = x), (Show s, nameof(Show.Episodes)) => EpisodeRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Show.ID == obj.ID || x.Show.Slug == obj.Slug) .Then(x => s.Episodes = x), (Show s, nameof(Show.Libraries)) => LibraryRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Shows.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => s.Libraries = x), (Show s, nameof(Show.Collections)) => CollectionRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Shows.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => s.Collections = x), // TODO Studio loading does not work. (Show s, nameof(Show.Studio)) => StudioRepository @@ -311,33 +313,37 @@ namespace Kyoo.Controllers .GetMetadataID(x => x.SeasonID == obj.ID) .Then(x => s.ExternalIDs = x), (Season s, nameof(Season.Episodes)) => EpisodeRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Season.ID == obj.ID || x.Season.Slug == obj.Slug) .Then(x => s.Episodes = x), (Season s, nameof(Season.Show)) => ShowRepository - .Get(Utility.ResourceEquals(obj)).Then(x => s.Show = x), + .Get(x => x.Seasons + .Any(y => y.ID == obj.ID || y.ShowID == s.ShowID && y.SeasonNumber == s.SeasonNumber)) + .Then(x => s.Show = x), + // TODO maybe add slug support for episodes (Episode e, nameof(Episode.ExternalIDs)) => ProviderRepository .GetMetadataID(x => x.EpisodeID == obj.ID) .Then(x => e.ExternalIDs = x), (Episode e, nameof(Episode.Tracks)) => TrackRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Episode.ID == obj.ID) .Then(x => e.Tracks = x), (Episode e, nameof(Episode.Show)) => ShowRepository - .Get(Utility.ResourceEquals(obj)).Then(x => e.Show = x), + .Get(x => x.Episodes.Any(y => y.ID == obj.ID)) + .Then(x => e.Show = x), (Episode e, nameof(Episode.Season)) => SeasonRepository - .Get(Utility.ResourceEquals(obj)) + .Get(x => x.Episodes.Any(y => y.ID == e.ID)) .Then(x => e.Season = x), (Track t, nameof(Track.Episode)) => EpisodeRepository - .Get(Utility.ResourceEquals(obj)) + .Get(x => x.Tracks.Any(y => y.ID == obj.ID)) .Then(x => t.Episode = x), (Genre g, nameof(Genre.Shows)) => ShowRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Genres.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) .Then(x => g.Shows = x), (Studio s, nameof(Studio.Shows)) => ShowRepository - .GetAll(Utility.ResourceEquals(obj)) + .GetAll(x => x.Studio.ID == obj.ID || x.Studio.Slug == obj.Slug) .Then(x => s.Shows = x), (People p, nameof(People.ExternalIDs)) => ProviderRepository diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index 1556753b..54f9ac89 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -11,7 +11,7 @@ namespace Kyoo.Models public string Name { get; set; } public string[] Paths { get; set; } - [EditableRelation] public virtual ICollection Providers { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection Providers { get; set; } [LoadableRelation] public virtual ICollection Shows { get; set; } [LoadableRelation] public virtual ICollection Collections { get; set; } diff --git a/Kyoo.Common/Models/Resources/ProviderID.cs b/Kyoo.Common/Models/Resources/ProviderID.cs index 411bf976..bb37b395 100644 --- a/Kyoo.Common/Models/Resources/ProviderID.cs +++ b/Kyoo.Common/Models/Resources/ProviderID.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -8,6 +9,8 @@ namespace Kyoo.Models public string Slug { get; set; } public string Name { get; set; } public string Logo { get; set; } + + [LoadableRelation] public ICollection Libraries { get; set; } public ProviderID() { } diff --git a/Kyoo.CommonAPI/ResourceViewAttribute.cs b/Kyoo.CommonAPI/ResourceViewAttribute.cs index 565c1e4b..7a60da55 100644 --- a/Kyoo.CommonAPI/ResourceViewAttribute.cs +++ b/Kyoo.CommonAPI/ResourceViewAttribute.cs @@ -24,9 +24,9 @@ namespace Kyoo.CommonApi where.Remove(key); } - string[] fields = string.Join(',', context.HttpContext.Request.Query["fields"]).Split(','); - - context.HttpContext.Items["fields"] = fields; + string[] fields = context.HttpContext.Request.Query["fields"] + .SelectMany(x => x.Split(',')) + .ToArray(); if (context.ActionDescriptor is ControllerActionDescriptor descriptor) { Type type = descriptor.MethodInfo.ReturnType; @@ -37,17 +37,24 @@ namespace Kyoo.CommonApi PropertyInfo[] properties = type.GetProperties() .Where(x => x.GetCustomAttribute() != null) .ToArray(); - foreach (string field in fields) - { - if (properties.Any(y => string.Equals(y.Name,field, StringComparison.InvariantCultureIgnoreCase))) - continue; - context.Result = new BadRequestObjectResult(new + fields = fields.Select(x => { - Error = $"{field} does not exist on {type.Name}." - }); + string property = properties + .FirstOrDefault(y => string.Equals(x, y.Name, StringComparison.InvariantCultureIgnoreCase)) + ?.Name; + if (property != null) + return property; + context.Result = new BadRequestObjectResult(new + { + Error = $"{x} does not exist on {type.Name}." + }); + return null; + }) + .ToArray(); + if (context.Result != null) return; - } } + context.HttpContext.Items["fields"] = fields; base.OnActionExecuting(context); } diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.Designer.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.Designer.cs index ece1dce0..ccd23f6a 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.Designer.cs @@ -7,10 +7,10 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -namespace Kyoo.Kyoo.Models.DatabaseMigrations.Internal +namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210216202218_Initial")] + [Migration("20210228210101_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.cs index e15d9e45..8a57b1cf 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210216202218_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -namespace Kyoo.Kyoo.Models.DatabaseMigrations.Internal +namespace Kyoo.Models.DatabaseMigrations.Internal { public partial class Initial : Migration { diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index c34b0309..6a2bf0b1 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -namespace Kyoo.Kyoo.Models.DatabaseMigrations.Internal +namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] partial class DatabaseContextModelSnapshot : ModelSnapshot From 072dd7d9b8ce6ab4bf35544c814b932f0ff27a34 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 28 Feb 2021 23:41:06 +0100 Subject: [PATCH 18/54] Reworking People's Role --- Kyoo.Common/Controllers/ILibraryManager.cs | 28 +++--- Kyoo.Common/Controllers/IRepository.cs | 28 +++--- .../Implementations/LibraryManager.cs | 55 +++++------ Kyoo.Common/Models/PeopleRole.cs | 95 +------------------ .../Repositories/PeopleRepository.cs | 30 +++--- Kyoo/Models/DatabaseContext.cs | 5 +- ....cs => 20210228224046_Initial.Designer.cs} | 2 +- ...1_Initial.cs => 20210228224046_Initial.cs} | 0 Kyoo/Views/API/PeopleApi.cs | 16 ++-- 9 files changed, 83 insertions(+), 176 deletions(-) rename Kyoo/Models/DatabaseMigrations/Internal/{20210228210101_Initial.Designer.cs => 20210228224046_Initial.Designer.cs} (99%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210228210101_Initial.cs => 20210228224046_Initial.cs} (100%) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index e6972d04..374f012a 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -117,25 +117,25 @@ namespace Kyoo.Controllers ) => GetPeopleFromShow(showSlug, where, new Sort(sort), limit); // Show Role relations - Task> GetRolesFromPeople(int showID, - Expression> where = null, - Sort sort = default, + Task> GetRolesFromPeople(int showID, + Expression> where = null, + Sort sort = default, Pagination limit = default); - Task> GetRolesFromPeople(int showID, - [Optional] Expression> where, - Expression> sort, + Task> GetRolesFromPeople(int showID, + [Optional] Expression> where, + Expression> sort, Pagination limit = default - ) => GetRolesFromPeople(showID, where, new Sort(sort), limit); + ) => GetRolesFromPeople(showID, where, new Sort(sort), limit); - Task> GetRolesFromPeople(string showSlug, - Expression> where = null, - Sort sort = default, + Task> GetRolesFromPeople(string showSlug, + Expression> where = null, + Sort sort = default, Pagination limit = default); - Task> GetRolesFromPeople(string showSlug, - [Optional] Expression> where, - Expression> sort, + Task> GetRolesFromPeople(string showSlug, + [Optional] Expression> where, + Expression> sort, Pagination limit = default - ) => GetRolesFromPeople(showSlug, where, new Sort(sort), limit); + ) => GetRolesFromPeople(showSlug, where, new Sort(sort), limit); // Helpers Task AddShowLink(int showID, int? libraryID, int? collectionID); diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 21f3318a..6ed573dc 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -186,25 +186,25 @@ namespace Kyoo.Controllers Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); - Task> GetFromPeople(int showID, - Expression> where = null, - Sort sort = default, + Task> GetFromPeople(int showID, + Expression> where = null, + Sort sort = default, Pagination limit = default); - Task> GetFromPeople(int showID, - [Optional] Expression> where, - Expression> sort, + Task> GetFromPeople(int showID, + [Optional] Expression> where, + Expression> sort, Pagination limit = default - ) => GetFromPeople(showID, where, new Sort(sort), limit); + ) => GetFromPeople(showID, where, new Sort(sort), limit); - Task> GetFromPeople(string showSlug, - Expression> where = null, - Sort sort = default, + Task> GetFromPeople(string showSlug, + Expression> where = null, + Sort sort = default, Pagination limit = default); - Task> GetFromPeople(string showSlug, - [Optional] Expression> where, - Expression> sort, + Task> GetFromPeople(string showSlug, + [Optional] Expression> where, + Expression> sort, Pagination limit = default - ) => GetFromPeople(showSlug, where, new Sort(sort), limit); + ) => GetFromPeople(showSlug, where, new Sort(sort), limit); } public interface IProviderRepository : IRepository diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 306e9bd7..18159706 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -266,61 +266,58 @@ namespace Kyoo.Controllers return (obj, member) switch { (Library l, nameof(Library.Providers)) => ProviderRepository - .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) + .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 || y.Slug == obj.Slug)) + .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 || y.Slug == obj.Slug)) + .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) .Then(x => l.Collections = x), (Collection c, nameof(Library.Shows)) => ShowRepository - .GetAll(x => x.Collections.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) + .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 || y.Slug == obj.Slug)) + .GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) .Then(x => c.Libraries = x), (Show s, nameof(Show.ExternalIDs)) => ProviderRepository .GetMetadataID(x => x.ShowID == obj.ID) .Then(x => s.ExternalIDs = x), (Show s, nameof(Show.Genres)) => GenreRepository - .GetAll(x => x.Shows.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) + .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Genres = x), - (Show s, nameof(Show.People)) => ( - PeopleRepository.GetFromShow((dynamic)(obj.ID > 0 ? obj.ID : obj.Slug)) - as Task>) + (Show s, nameof(Show.People)) => PeopleRepository + .GetFromShow(obj.ID) .Then(x => s.People = x), (Show s, nameof(Show.Seasons)) => SeasonRepository - .GetAll(x => x.Show.ID == obj.ID || x.Show.Slug == obj.Slug) + .GetAll(x => x.Show.ID == obj.ID) .Then(x => s.Seasons = x), (Show s, nameof(Show.Episodes)) => EpisodeRepository - .GetAll(x => x.Show.ID == obj.ID || x.Show.Slug == obj.Slug) + .GetAll(x => x.Show.ID == obj.ID) .Then(x => s.Episodes = x), (Show s, nameof(Show.Libraries)) => LibraryRepository - .GetAll(x => x.Shows.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) + .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 || y.Slug == obj.Slug)) + .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Collections = x), // TODO Studio loading does not work. (Show s, nameof(Show.Studio)) => StudioRepository - .Get(x => x.Shows.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) + .Get(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Studio = x), (Season s, nameof(Season.ExternalIDs)) => ProviderRepository .GetMetadataID(x => x.SeasonID == obj.ID) .Then(x => s.ExternalIDs = x), (Season s, nameof(Season.Episodes)) => EpisodeRepository - .GetAll(x => x.Season.ID == obj.ID || x.Season.Slug == obj.Slug) + .GetAll(x => x.Season.ID == obj.ID) .Then(x => s.Episodes = x), (Season s, nameof(Season.Show)) => ShowRepository - .Get(x => x.Seasons - .Any(y => y.ID == obj.ID || y.ShowID == s.ShowID && y.SeasonNumber == s.SeasonNumber)) + .Get(x => x.Seasons.Any(y => y.ID == obj.ID)) .Then(x => s.Show = x), - // TODO maybe add slug support for episodes (Episode e, nameof(Episode.ExternalIDs)) => ProviderRepository .GetMetadataID(x => x.EpisodeID == obj.ID) .Then(x => e.ExternalIDs = x), @@ -339,19 +336,19 @@ namespace Kyoo.Controllers .Then(x => t.Episode = x), (Genre g, nameof(Genre.Shows)) => ShowRepository - .GetAll(x => x.Genres.Any(y => y.ID == obj.ID || y.Slug == obj.Slug)) + .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 || x.Studio.Slug == obj.Slug) + .GetAll(x => x.Studio.ID == obj.ID) .Then(x => s.Shows = x), (People p, nameof(People.ExternalIDs)) => ProviderRepository .GetMetadataID(x => x.PeopleID == obj.ID) .Then(x => p.ExternalIDs = x), - (People p, nameof(People.Roles)) => ( - PeopleRepository.GetFromPeople((dynamic)(obj.ID > 0 ? obj.ID : obj.Slug)) - as Task>).Then(x => p.Roles = x), + (People p, nameof(People.Roles)) => PeopleRepository + .GetFromPeople(obj.ID) + .Then(x => p.Roles = x), _ => throw new ArgumentException($"Couldn't find a way to load {member} of {obj.Slug}.") }; @@ -459,17 +456,17 @@ namespace Kyoo.Controllers return PeopleRepository.GetFromShow(showSlug, where, sort, limit); } - public Task> GetRolesFromPeople(int id, - Expression> where = null, - Sort sort = default, + public Task> GetRolesFromPeople(int id, + Expression> where = null, + Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromPeople(id, where, sort, limit); } - public Task> GetRolesFromPeople(string slug, - Expression> where = null, - Sort sort = default, + public Task> GetRolesFromPeople(string slug, + Expression> where = null, + Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromPeople(slug, where, sort, limit); diff --git a/Kyoo.Common/Models/PeopleRole.cs b/Kyoo.Common/Models/PeopleRole.cs index fbde0d5e..533f0fca 100644 --- a/Kyoo.Common/Models/PeopleRole.cs +++ b/Kyoo.Common/Models/PeopleRole.cs @@ -9,37 +9,11 @@ namespace Kyoo.Models public class PeopleRole : IResource { [SerializeIgnore] public int ID { get; set; } + [SerializeIgnore] public string Slug => ForPeople ? Show.Slug : People.Slug; + [SerializeIgnore] public bool ForPeople; [SerializeIgnore] public int PeopleID { get; set; } + // TODO implement a SerializeInline for People or Show depending on the context. [SerializeIgnore] public virtual People People { get; set; } - - [ExpressionRewrite(nameof(People) + "." + nameof(Models.People.Slug))] - public string Slug - { - get => People.Slug; - set => People.Slug = value; - } - - [ExpressionRewrite(nameof(People) + "."+ nameof(Models.People.Name))] - public string Name - { - get => People.Name; - set => People.Name = value; - } - - [ExpressionRewrite(nameof(People) + "."+ nameof(Models.People.Poster))] - public string Poster - { - get => People.Poster; - set => People.Poster = value; - } - - [ExpressionRewrite(nameof(People) + "."+ nameof(Models.People.ExternalIDs))] - public ICollection ExternalIDs - { - get => People.ExternalIDs; - set => People.ExternalIDs = value; - } - [SerializeIgnore] public int ShowID { get; set; } [SerializeIgnore] public virtual Show Show { get; set; } public string Role { get; set; } @@ -67,67 +41,4 @@ namespace Kyoo.Models Type = type; } } - - public class ShowRole : IResource - { - public int ID { get; set; } - public string Role { get; set; } - public string Type { get; set; } - - public string Slug { get; set; } - public string Title { get; set; } - public ICollection Aliases { get; set; } - [SerializeIgnore] public string Path { get; set; } - public string Overview { get; set; } - public Status? Status { get; set; } - public string TrailerUrl { get; set; } - public int? StartYear { get; set; } - public int? EndYear { get; set; } - public string Poster { get; set; } - public string Logo { get; set; } - public string Backdrop { get; set; } - public bool IsMovie { get; set; } - - public ShowRole() {} - - public ShowRole(PeopleRole x) - { - ID = x.ID; - Role = x.Role; - Type = x.Type; - Slug = x.Show.Slug; - Title = x.Show.Title; - Aliases = x.Show.Aliases?.ToArray(); - Path = x.Show.Path; - Overview = x.Show.Overview; - Status = x.Show.Status; - TrailerUrl = x.Show.TrailerUrl; - StartYear = x.Show.StartYear; - EndYear = x.Show.EndYear; - Poster = x.Show.Poster; - Logo = x.Show.Logo; - Backdrop = x.Show.Backdrop; - IsMovie = x.Show.IsMovie; - } - - public static Expression> FromPeopleRole => x => new ShowRole - { - ID = x.ID, - Role = x.Role, - Type = x.Type, - Slug = x.Show.Slug, - Title = x.Show.Title, - Aliases = x.Show.Aliases != null ? x.Show.Aliases.ToArray() : null, - Path = x.Show.Path, - Overview = x.Show.Overview, - Status = x.Show.Status, - TrailerUrl = x.Show.TrailerUrl, - StartYear = x.Show.StartYear, - EndYear = x.Show.EndYear, - Poster = x.Show.Poster, - Logo = x.Show.Logo, - Backdrop = x.Show.Backdrop, - IsMovie = x.Show.IsMovie - }; - } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index a0f7c0bb..f537e59b 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -126,37 +126,39 @@ namespace Kyoo.Controllers return people; } - public async Task> GetFromPeople(int peopleID, - Expression> where = null, - Sort sort = default, + public async Task> GetFromPeople(int peopleID, + Expression> where = null, + Sort sort = default, Pagination limit = default) { - ICollection roles = await ApplyFilters(_database.PeopleRoles.Where(x => x.PeopleID == peopleID) - .Select(ShowRole.FromPeopleRole), - async id => new ShowRole(await _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id)), - x => x.Title, + ICollection roles = await ApplyFilters(_database.PeopleRoles.Where(x => x.PeopleID == peopleID), + id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), + x => x.Show.Title, where, sort, limit); if (!roles.Any() && await Get(peopleID) == null) throw new ItemNotFound(); + foreach (PeopleRole role in roles) + role.ForPeople = true; return roles; } - public async Task> GetFromPeople(string slug, - Expression> where = null, - Sort sort = default, + public async Task> GetFromPeople(string slug, + Expression> where = null, + Sort sort = default, Pagination limit = default) { - ICollection roles = await ApplyFilters(_database.PeopleRoles.Where(x => x.People.Slug == slug) - .Select(ShowRole.FromPeopleRole), - async id => new ShowRole(await _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id)), - x => x.Title, + ICollection roles = await ApplyFilters(_database.PeopleRoles.Where(x => x.People.Slug == slug), + id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), + x => x.Show.Title, where, sort, limit); if (!roles.Any() && await Get(slug) == null) throw new ItemNotFound(); + foreach (PeopleRole role in roles) + role.ForPeople = true; return roles; } } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index e7470268..4ab750f3 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -98,10 +98,7 @@ namespace Kyoo .Ignore(x => x.Collections); modelBuilder.Entity() - .Ignore(x => x.Slug) - .Ignore(x => x.Name) - .Ignore(x => x.Poster) - .Ignore(x => x.ExternalIDs); + .Ignore(x => x.Slug); modelBuilder.Entity() .Ignore(x => x.Shows); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.Designer.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.Designer.cs index ccd23f6a..d08a2a98 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210228210101_Initial")] + [Migration("20210228224046_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.cs similarity index 100% rename from Kyoo/Models/DatabaseMigrations/Internal/20210228210101_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.cs diff --git a/Kyoo/Views/API/PeopleApi.cs b/Kyoo/Views/API/PeopleApi.cs index a264363c..88088757 100644 --- a/Kyoo/Views/API/PeopleApi.cs +++ b/Kyoo/Views/API/PeopleApi.cs @@ -29,7 +29,7 @@ namespace Kyoo.Api [HttpGet("{id:int}/role")] [HttpGet("{id:int}/roles")] [Authorize(Policy = "Read")] - public async Task>> GetRoles(int id, + public async Task>> GetRoles(int id, [FromQuery] string sortBy, [FromQuery] int afterID, [FromQuery] Dictionary where, @@ -37,9 +37,9 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetRolesFromPeople(id, - ApiHelper.ParseWhere(where), - new Sort(sortBy), + ICollection resources = await _libraryManager.GetRolesFromPeople(id, + ApiHelper.ParseWhere(where), + new Sort(sortBy), new Pagination(limit, afterID)); return Page(resources, limit); @@ -57,7 +57,7 @@ namespace Kyoo.Api [HttpGet("{slug}/role")] [HttpGet("{slug}/roles")] [Authorize(Policy = "Read")] - public async Task>> GetRoles(string slug, + public async Task>> GetRoles(string slug, [FromQuery] string sortBy, [FromQuery] int afterID, [FromQuery] Dictionary where, @@ -65,9 +65,9 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetRolesFromPeople(slug, - ApiHelper.ParseWhere(where), - new Sort(sortBy), + ICollection resources = await _libraryManager.GetRolesFromPeople(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), new Pagination(limit, afterID)); return Page(resources, limit); From b2b53f269174136187c32d5ba9b088abe84b031a Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 1 Mar 2021 00:20:38 +0100 Subject: [PATCH 19/54] Removing links & DE items. DotnetEf now support Many to Many out of the box --- .../Repositories/LibraryItemRepository.cs | 25 +- .../Repositories/ShowRepository.cs | 6 +- Kyoo/Models/DatabaseContext.cs | 115 +------ ....cs => 20210228232014_Initial.Designer.cs} | 298 ++++++++++-------- ...6_Initial.cs => 20210228232014_Initial.cs} | 184 +++++++---- .../Internal/DatabaseContextModelSnapshot.cs | 296 +++++++++-------- Kyoo/Tasks/CoreTaskHolder.cs | 2 +- Kyoo/Tasks/ReScan.cs | 254 +++++++-------- 8 files changed, 606 insertions(+), 574 deletions(-) rename Kyoo/Models/DatabaseMigrations/Internal/{20210228224046_Initial.Designer.cs => 20210228232014_Initial.Designer.cs} (80%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210228224046_Initial.cs => 20210228232014_Initial.cs} (81%) diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index b8c5cc17..0119409c 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -71,7 +71,7 @@ namespace Kyoo.Controllers private IQueryable ItemsQuery => _database.Shows - .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) + // .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) .Select(LibraryItem.FromShow) .Concat(_database.Collections .Select(LibraryItem.FromCollection)); @@ -114,17 +114,18 @@ namespace Kyoo.Controllers public override Task Delete(LibraryItem obj) => throw new InvalidOperationException(); private IQueryable LibraryRelatedQuery(Expression> selector) - => _database.LibraryLinks - .Where(selector) - .Select(x => x.Show) - .Where(x => x != null) - .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) - .Select(LibraryItem.FromShow) - .Concat(_database.LibraryLinks - .Where(selector) - .Select(x => x.Collection) - .Where(x => x != null) - .Select(LibraryItem.FromCollection)); + => throw new NotImplementedException(); + // => _database.LibraryLinks + // .Where(selector) + // .Select(x => x.Show) + // .Where(x => x != null) + // .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) + // .Select(LibraryItem.FromShow) + // .Concat(_database.LibraryLinks + // .Where(selector) + // .Select(x => x.Collection) + // .Where(x => x != null) + // .Select(LibraryItem.FromCollection)); public async Task> GetFromLibrary(int id, Expression> where = null, diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index f1f0fef4..339ade96 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -134,18 +134,18 @@ namespace Kyoo.Controllers { if (collectionID != null) { - await _database.CollectionLinks.AddAsync(new CollectionLink {ParentID = collectionID.Value, ChildID = showID}); + // await _database.CollectionLinks.AddAsync(new CollectionLink {ParentID = collectionID.Value, ChildID = showID}); await _database.SaveIfNoDuplicates(); } if (libraryID != null) { - await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, ShowID = showID}); + // await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, ShowID = showID}); await _database.SaveIfNoDuplicates(); } if (libraryID != null && collectionID != null) { - await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, CollectionID = collectionID.Value}); + // await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, CollectionID = collectionID.Value}); await _database.SaveIfNoDuplicates(); } } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 4ab750f3..dc10c390 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -16,13 +16,13 @@ namespace Kyoo { public DatabaseContext(DbContextOptions options) : base(options) { } - public DbSet Libraries { get; set; } - public DbSet Collections { get; set; } - public DbSet Shows { get; set; } + public DbSet Libraries { get; set; } + public DbSet Collections { get; set; } + public DbSet Shows { get; set; } public DbSet Seasons { get; set; } public DbSet Episodes { get; set; } public DbSet Tracks { get; set; } - public DbSet Genres { get; set; } + public DbSet Genres { get; set; } public DbSet People { get; set; } public DbSet Studios { get; set; } public DbSet Providers { get; set; } @@ -30,11 +30,6 @@ namespace Kyoo public DbSet PeopleRoles { get; set; } - - public DbSet LibraryLinks { get; set; } - public DbSet CollectionLinks { get; set; } - public DbSet GenreLinks { get; set; } - public DbSet ProviderLinks { get; set; } public DatabaseContext() { @@ -56,11 +51,11 @@ namespace Kyoo modelBuilder.Ignore(); modelBuilder.Ignore(); - modelBuilder.Entity() + modelBuilder.Entity() .Property(x => x.Paths) .HasColumnType("text[]"); - modelBuilder.Entity() + modelBuilder.Entity() .Property(x => x.Aliases) .HasColumnType("text[]"); @@ -72,88 +67,8 @@ namespace Kyoo .Property(t => t.IsForced) .ValueGeneratedNever(); - - modelBuilder.Entity() - .HasKey(x => new {ShowID = x.ParentID, GenreID = x.ChildID}); - - modelBuilder.Entity() - .HasKey(x => new {CollectionID = x.ParentID, ShowID = x.ChildID}); - - modelBuilder.Entity() - .HasKey(x => new {LibraryID = x.ParentID, ProviderID = x.ChildID}); - - - modelBuilder.Entity() - .Ignore(x => x.Shows) - .Ignore(x => x.Collections) - .Ignore(x => x.Providers); - - modelBuilder.Entity() - .Ignore(x => x.Shows) - .Ignore(x => x.Libraries); - - modelBuilder.Entity() - .Ignore(x => x.Genres) - .Ignore(x => x.Libraries) - .Ignore(x => x.Collections); - - modelBuilder.Entity() - .Ignore(x => x.Slug); - - modelBuilder.Entity() - .Ignore(x => x.Shows); - - - modelBuilder.Entity() - .HasOne(x => x.Library as LibraryDE) - .WithMany(x => x.Links) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(x => x.Show as ShowDE) - .WithMany(x => x.LibraryLinks) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(x => x.Collection as CollectionDE) - .WithMany(x => x.LibraryLinks) - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasOne(x => x.Parent as CollectionDE) - .WithMany(x => x.Links) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(x => x.Child as ShowDE) - .WithMany(x => x.CollectionLinks) - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasOne(x => x.Child as GenreDE) - .WithMany(x => x.Links) - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(x => x.Parent as ShowDE) - .WithMany(x => x.GenreLinks) - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasOne(x => x.Parent as LibraryDE) - .WithMany(x => x.ProviderLinks) - .OnDelete(DeleteBehavior.Cascade); - - modelBuilder.Entity() - .HasOne(x => x.Show as ShowDE) - .WithMany(x => x.Seasons); - modelBuilder.Entity() - .HasOne(x => x.Show as ShowDE) - .WithMany(x => x.Episodes); - modelBuilder.Entity() - .HasOne(x => x.Show as ShowDE) - .WithMany(x => x.People); - - - modelBuilder.Entity() - .HasOne(x => x.Show as ShowDE) + .HasOne(x => x.Show) .WithMany(x => x.ExternalIDs) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() @@ -169,27 +84,27 @@ namespace Kyoo .WithMany(x => x.ExternalIDs) .OnDelete(DeleteBehavior.Cascade); - 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().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); - modelBuilder.Entity() + modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); - modelBuilder.Entity() + modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); - modelBuilder.Entity() + modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); - modelBuilder.Entity() + modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.Designer.cs similarity index 80% rename from Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.Designer.cs index d08a2a98..bfdf578f 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210228224046_Initial")] + [Migration("20210228232014_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -24,7 +24,52 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasAnnotation("ProductVersion", "5.0.3") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - modelBuilder.Entity("Kyoo.Models.CollectionDE", b => + modelBuilder.Entity("CollectionLibrary", b => + { + b.Property("CollectionsID") + .HasColumnType("integer"); + + b.Property("LibrariesID") + .HasColumnType("integer"); + + b.HasKey("CollectionsID", "LibrariesID"); + + b.HasIndex("LibrariesID"); + + b.ToTable("CollectionLibrary"); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.Property("CollectionsID") + .HasColumnType("integer"); + + b.Property("ShowsID") + .HasColumnType("integer"); + + b.HasKey("CollectionsID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("CollectionShow"); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.Property("GenresID") + .HasColumnType("integer"); + + b.Property("ShowsID") + .HasColumnType("integer"); + + b.HasKey("GenresID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("GenreShow"); + }); + + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -52,21 +97,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Collections"); }); - modelBuilder.Entity("Kyoo.Models.CollectionLink", b => - { - b.Property("ParentID") - .HasColumnType("integer"); - - b.Property("ChildID") - .HasColumnType("integer"); - - b.HasKey("ParentID", "ChildID"); - - b.HasIndex("ChildID"); - - b.ToTable("CollectionLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Episode", b => { b.Property("ID") @@ -117,7 +147,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Episodes"); }); - modelBuilder.Entity("Kyoo.Models.GenreDE", b => + modelBuilder.Entity("Kyoo.Models.Genre", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -139,22 +169,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Genres"); }); - modelBuilder.Entity("Kyoo.Models.GenreLink", b => - { - b.Property("ParentID") - .HasColumnType("integer"); - - b.Property("ChildID") - .HasColumnType("integer"); - - b.HasKey("ParentID", "ChildID"); - - b.HasIndex("ChildID"); - - b.ToTable("GenreLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.LibraryDE", b => + modelBuilder.Entity("Kyoo.Models.Library", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -207,7 +222,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("LibraryID", "ShowID") .IsUnique(); - b.ToTable("LibraryLinks"); + b.ToTable("LibraryLink"); }); modelBuilder.Entity("Kyoo.Models.MetadataID", b => @@ -331,21 +346,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Providers"); }); - modelBuilder.Entity("Kyoo.Models.ProviderLink", b => - { - b.Property("ParentID") - .HasColumnType("integer"); - - b.Property("ChildID") - .HasColumnType("integer"); - - b.HasKey("ParentID", "ChildID"); - - b.HasIndex("ChildID"); - - b.ToTable("ProviderLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Property("ID") @@ -379,7 +379,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Seasons"); }); - modelBuilder.Entity("Kyoo.Models.ShowDE", b => + modelBuilder.Entity("Kyoo.Models.Show", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -502,23 +502,79 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.CollectionLink", b => + modelBuilder.Entity("LibraryProviderID", b => { - b.HasOne("Kyoo.Models.ShowDE", "Child") - .WithMany("CollectionLinks") - .HasForeignKey("ChildID") + b.Property("LibrariesID") + .HasColumnType("integer"); + + b.Property("ProvidersID") + .HasColumnType("integer"); + + b.HasKey("LibrariesID", "ProvidersID"); + + b.HasIndex("ProvidersID"); + + b.ToTable("LibraryProviderID"); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.Property("LibrariesID") + .HasColumnType("integer"); + + b.Property("ShowsID") + .HasColumnType("integer"); + + b.HasKey("LibrariesID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LibraryShow"); + }); + + modelBuilder.Entity("CollectionLibrary", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Kyoo.Models.CollectionDE", "Parent") - .WithMany("Links") - .HasForeignKey("ParentID") + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Child"); + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); - b.Navigation("Parent"); + modelBuilder.Entity("GenreShow", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -527,7 +583,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .WithMany("Episodes") .HasForeignKey("SeasonID"); - b.HasOne("Kyoo.Models.ShowDE", "Show") + b.HasOne("Kyoo.Models.Show", "Show") .WithMany("Episodes") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) @@ -538,42 +594,21 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.GenreLink", b => - { - b.HasOne("Kyoo.Models.GenreDE", "Child") - .WithMany("Links") - .HasForeignKey("ChildID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.ShowDE", "Parent") - .WithMany("GenreLinks") - .HasForeignKey("ParentID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Child"); - - b.Navigation("Parent"); - }); - modelBuilder.Entity("Kyoo.Models.LibraryLink", b => { - b.HasOne("Kyoo.Models.CollectionDE", "Collection") - .WithMany("LibraryLinks") - .HasForeignKey("CollectionID") - .OnDelete(DeleteBehavior.Cascade); + b.HasOne("Kyoo.Models.Collection", "Collection") + .WithMany() + .HasForeignKey("CollectionID"); - b.HasOne("Kyoo.Models.LibraryDE", "Library") - .WithMany("Links") + b.HasOne("Kyoo.Models.Library", "Library") + .WithMany() .HasForeignKey("LibraryID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Kyoo.Models.ShowDE", "Show") - .WithMany("LibraryLinks") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade); + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany() + .HasForeignKey("ShowID"); b.Navigation("Collection"); @@ -605,7 +640,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("SeasonID") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Kyoo.Models.ShowDE", "Show") + b.HasOne("Kyoo.Models.Show", "Show") .WithMany("ExternalIDs") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade); @@ -629,7 +664,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Kyoo.Models.ShowDE", "Show") + b.HasOne("Kyoo.Models.Show", "Show") .WithMany("People") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) @@ -640,28 +675,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.ProviderLink", b => - { - b.HasOne("Kyoo.Models.ProviderID", "Child") - .WithMany() - .HasForeignKey("ChildID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.LibraryDE", "Parent") - .WithMany("ProviderLinks") - .HasForeignKey("ParentID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Child"); - - b.Navigation("Parent"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { - b.HasOne("Kyoo.Models.ShowDE", "Show") + b.HasOne("Kyoo.Models.Show", "Show") .WithMany("Seasons") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) @@ -670,10 +686,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.ShowDE", b => + modelBuilder.Entity("Kyoo.Models.Show", b => { b.HasOne("Kyoo.Models.Studio", "Studio") - .WithMany() + .WithMany("Shows") .HasForeignKey("StudioID"); b.Navigation("Studio"); @@ -690,11 +706,34 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Episode"); }); - modelBuilder.Entity("Kyoo.Models.CollectionDE", b => + modelBuilder.Entity("LibraryProviderID", b => { - b.Navigation("LibraryLinks"); + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Links"); + b.HasOne("Kyoo.Models.ProviderID", null) + .WithMany() + .HasForeignKey("ProvidersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -704,18 +743,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.GenreDE", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Kyoo.Models.LibraryDE", b => - { - b.Navigation("Links"); - - b.Navigation("ProviderLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -730,22 +757,21 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("ExternalIDs"); }); - modelBuilder.Entity("Kyoo.Models.ShowDE", b => + modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Navigation("Shows"); + }); #pragma warning restore 612, 618 } } diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.cs similarity index 81% rename from Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.cs index 8a57b1cf..d211a75c 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210228224046_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.cs @@ -103,24 +103,48 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }); migrationBuilder.CreateTable( - name: "ProviderLinks", + name: "CollectionLibrary", columns: table => new { - ParentID = table.Column(type: "integer", nullable: false), - ChildID = table.Column(type: "integer", nullable: false) + CollectionsID = table.Column(type: "integer", nullable: false), + LibrariesID = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_ProviderLinks", x => new { x.ParentID, x.ChildID }); + table.PrimaryKey("PK_CollectionLibrary", x => new { x.CollectionsID, x.LibrariesID }); table.ForeignKey( - name: "FK_ProviderLinks_Libraries_ParentID", - column: x => x.ParentID, + name: "FK_CollectionLibrary_Collections_CollectionsID", + column: x => x.CollectionsID, + principalTable: "Collections", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CollectionLibrary_Libraries_LibrariesID", + column: x => x.LibrariesID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LibraryProviderID", + columns: table => new + { + LibrariesID = table.Column(type: "integer", nullable: false), + ProvidersID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LibraryProviderID", x => new { x.LibrariesID, x.ProvidersID }); + table.ForeignKey( + name: "FK_LibraryProviderID_Libraries_LibrariesID", + column: x => x.LibrariesID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_ProviderLinks_Providers_ChildID", - column: x => x.ChildID, + name: "FK_LibraryProviderID_Providers_ProvidersID", + column: x => x.ProvidersID, principalTable: "Providers", principalColumn: "ID", onDelete: ReferentialAction.Cascade); @@ -159,55 +183,55 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }); migrationBuilder.CreateTable( - name: "CollectionLinks", + name: "CollectionShow", columns: table => new { - ParentID = table.Column(type: "integer", nullable: false), - ChildID = table.Column(type: "integer", nullable: false) + CollectionsID = table.Column(type: "integer", nullable: false), + ShowsID = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_CollectionLinks", x => new { x.ParentID, x.ChildID }); + table.PrimaryKey("PK_CollectionShow", x => new { x.CollectionsID, x.ShowsID }); table.ForeignKey( - name: "FK_CollectionLinks_Collections_ParentID", - column: x => x.ParentID, + name: "FK_CollectionShow_Collections_CollectionsID", + column: x => x.CollectionsID, principalTable: "Collections", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_CollectionLinks_Shows_ChildID", - column: x => x.ChildID, + name: "FK_CollectionShow_Shows_ShowsID", + column: x => x.ShowsID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "GenreLinks", + name: "GenreShow", columns: table => new { - ParentID = table.Column(type: "integer", nullable: false), - ChildID = table.Column(type: "integer", nullable: false) + GenresID = table.Column(type: "integer", nullable: false), + ShowsID = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_GenreLinks", x => new { x.ParentID, x.ChildID }); + table.PrimaryKey("PK_GenreShow", x => new { x.GenresID, x.ShowsID }); table.ForeignKey( - name: "FK_GenreLinks_Genres_ChildID", - column: x => x.ChildID, + name: "FK_GenreShow_Genres_GenresID", + column: x => x.GenresID, principalTable: "Genres", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_GenreLinks_Shows_ParentID", - column: x => x.ParentID, + name: "FK_GenreShow_Shows_ShowsID", + column: x => x.ShowsID, principalTable: "Shows", principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "LibraryLinks", + name: "LibraryLink", columns: table => new { ID = table.Column(type: "integer", nullable: false) @@ -218,24 +242,48 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }, constraints: table => { - table.PrimaryKey("PK_LibraryLinks", x => x.ID); + table.PrimaryKey("PK_LibraryLink", x => x.ID); table.ForeignKey( - name: "FK_LibraryLinks_Collections_CollectionID", + name: "FK_LibraryLink_Collections_CollectionID", column: x => x.CollectionID, principalTable: "Collections", principalColumn: "ID", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Restrict); table.ForeignKey( - name: "FK_LibraryLinks_Libraries_LibraryID", + name: "FK_LibraryLink_Libraries_LibraryID", column: x => x.LibraryID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_LibraryLinks_Shows_ShowID", + name: "FK_LibraryLink_Shows_ShowID", column: x => x.ShowID, principalTable: "Shows", principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "LibraryShow", + columns: table => new + { + LibrariesID = table.Column(type: "integer", nullable: false), + ShowsID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LibraryShow", x => new { x.LibrariesID, x.ShowsID }); + table.ForeignKey( + name: "FK_LibraryShow_Libraries_LibrariesID", + column: x => x.LibrariesID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LibraryShow_Shows_ShowsID", + column: x => x.ShowsID, + principalTable: "Shows", + principalColumn: "ID", onDelete: ReferentialAction.Cascade); }); @@ -403,9 +451,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }); migrationBuilder.CreateIndex( - name: "IX_CollectionLinks_ChildID", - table: "CollectionLinks", - column: "ChildID"); + name: "IX_CollectionLibrary_LibrariesID", + table: "CollectionLibrary", + column: "LibrariesID"); migrationBuilder.CreateIndex( name: "IX_Collections_Slug", @@ -413,6 +461,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_CollectionShow_ShowsID", + table: "CollectionShow", + column: "ShowsID"); + migrationBuilder.CreateIndex( name: "IX_Episodes_SeasonID", table: "Episodes", @@ -424,17 +477,17 @@ namespace Kyoo.Models.DatabaseMigrations.Internal columns: new[] { "ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber" }, unique: true); - migrationBuilder.CreateIndex( - name: "IX_GenreLinks_ChildID", - table: "GenreLinks", - column: "ChildID"); - migrationBuilder.CreateIndex( name: "IX_Genres_Slug", table: "Genres", column: "Slug", unique: true); + migrationBuilder.CreateIndex( + name: "IX_GenreShow_ShowsID", + table: "GenreShow", + column: "ShowsID"); + migrationBuilder.CreateIndex( name: "IX_Libraries_Slug", table: "Libraries", @@ -442,27 +495,37 @@ namespace Kyoo.Models.DatabaseMigrations.Internal unique: true); migrationBuilder.CreateIndex( - name: "IX_LibraryLinks_CollectionID", - table: "LibraryLinks", + name: "IX_LibraryLink_CollectionID", + table: "LibraryLink", column: "CollectionID"); migrationBuilder.CreateIndex( - name: "IX_LibraryLinks_LibraryID_CollectionID", - table: "LibraryLinks", + name: "IX_LibraryLink_LibraryID_CollectionID", + table: "LibraryLink", columns: new[] { "LibraryID", "CollectionID" }, unique: true); migrationBuilder.CreateIndex( - name: "IX_LibraryLinks_LibraryID_ShowID", - table: "LibraryLinks", + name: "IX_LibraryLink_LibraryID_ShowID", + table: "LibraryLink", columns: new[] { "LibraryID", "ShowID" }, unique: true); migrationBuilder.CreateIndex( - name: "IX_LibraryLinks_ShowID", - table: "LibraryLinks", + name: "IX_LibraryLink_ShowID", + table: "LibraryLink", column: "ShowID"); + migrationBuilder.CreateIndex( + name: "IX_LibraryProviderID_ProvidersID", + table: "LibraryProviderID", + column: "ProvidersID"); + + migrationBuilder.CreateIndex( + name: "IX_LibraryShow_ShowsID", + table: "LibraryShow", + column: "ShowsID"); + migrationBuilder.CreateIndex( name: "IX_MetadataIds_EpisodeID", table: "MetadataIds", @@ -504,11 +567,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal table: "PeopleRoles", column: "ShowID"); - migrationBuilder.CreateIndex( - name: "IX_ProviderLinks_ChildID", - table: "ProviderLinks", - column: "ChildID"); - migrationBuilder.CreateIndex( name: "IX_Providers_Slug", table: "Providers", @@ -547,13 +605,22 @@ namespace Kyoo.Models.DatabaseMigrations.Internal protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "CollectionLinks"); + name: "CollectionLibrary"); migrationBuilder.DropTable( - name: "GenreLinks"); + name: "CollectionShow"); migrationBuilder.DropTable( - name: "LibraryLinks"); + name: "GenreShow"); + + migrationBuilder.DropTable( + name: "LibraryLink"); + + migrationBuilder.DropTable( + name: "LibraryProviderID"); + + migrationBuilder.DropTable( + name: "LibraryShow"); migrationBuilder.DropTable( name: "MetadataIds"); @@ -561,9 +628,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal migrationBuilder.DropTable( name: "PeopleRoles"); - migrationBuilder.DropTable( - name: "ProviderLinks"); - migrationBuilder.DropTable( name: "Tracks"); @@ -573,15 +637,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal migrationBuilder.DropTable( name: "Collections"); - migrationBuilder.DropTable( - name: "People"); - migrationBuilder.DropTable( name: "Libraries"); migrationBuilder.DropTable( name: "Providers"); + migrationBuilder.DropTable( + name: "People"); + migrationBuilder.DropTable( name: "Episodes"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 6a2bf0b1..2d9d6b75 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -22,7 +22,52 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasAnnotation("ProductVersion", "5.0.3") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - modelBuilder.Entity("Kyoo.Models.CollectionDE", b => + modelBuilder.Entity("CollectionLibrary", b => + { + b.Property("CollectionsID") + .HasColumnType("integer"); + + b.Property("LibrariesID") + .HasColumnType("integer"); + + b.HasKey("CollectionsID", "LibrariesID"); + + b.HasIndex("LibrariesID"); + + b.ToTable("CollectionLibrary"); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.Property("CollectionsID") + .HasColumnType("integer"); + + b.Property("ShowsID") + .HasColumnType("integer"); + + b.HasKey("CollectionsID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("CollectionShow"); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.Property("GenresID") + .HasColumnType("integer"); + + b.Property("ShowsID") + .HasColumnType("integer"); + + b.HasKey("GenresID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("GenreShow"); + }); + + modelBuilder.Entity("Kyoo.Models.Collection", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -50,21 +95,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Collections"); }); - modelBuilder.Entity("Kyoo.Models.CollectionLink", b => - { - b.Property("ParentID") - .HasColumnType("integer"); - - b.Property("ChildID") - .HasColumnType("integer"); - - b.HasKey("ParentID", "ChildID"); - - b.HasIndex("ChildID"); - - b.ToTable("CollectionLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Episode", b => { b.Property("ID") @@ -115,7 +145,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Episodes"); }); - modelBuilder.Entity("Kyoo.Models.GenreDE", b => + modelBuilder.Entity("Kyoo.Models.Genre", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -137,22 +167,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Genres"); }); - modelBuilder.Entity("Kyoo.Models.GenreLink", b => - { - b.Property("ParentID") - .HasColumnType("integer"); - - b.Property("ChildID") - .HasColumnType("integer"); - - b.HasKey("ParentID", "ChildID"); - - b.HasIndex("ChildID"); - - b.ToTable("GenreLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.LibraryDE", b => + modelBuilder.Entity("Kyoo.Models.Library", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -205,7 +220,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("LibraryID", "ShowID") .IsUnique(); - b.ToTable("LibraryLinks"); + b.ToTable("LibraryLink"); }); modelBuilder.Entity("Kyoo.Models.MetadataID", b => @@ -329,21 +344,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Providers"); }); - modelBuilder.Entity("Kyoo.Models.ProviderLink", b => - { - b.Property("ParentID") - .HasColumnType("integer"); - - b.Property("ChildID") - .HasColumnType("integer"); - - b.HasKey("ParentID", "ChildID"); - - b.HasIndex("ChildID"); - - b.ToTable("ProviderLinks"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { b.Property("ID") @@ -377,7 +377,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Seasons"); }); - modelBuilder.Entity("Kyoo.Models.ShowDE", b => + modelBuilder.Entity("Kyoo.Models.Show", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -500,23 +500,79 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.CollectionLink", b => + modelBuilder.Entity("LibraryProviderID", b => { - b.HasOne("Kyoo.Models.ShowDE", "Child") - .WithMany("CollectionLinks") - .HasForeignKey("ChildID") + b.Property("LibrariesID") + .HasColumnType("integer"); + + b.Property("ProvidersID") + .HasColumnType("integer"); + + b.HasKey("LibrariesID", "ProvidersID"); + + b.HasIndex("ProvidersID"); + + b.ToTable("LibraryProviderID"); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.Property("LibrariesID") + .HasColumnType("integer"); + + b.Property("ShowsID") + .HasColumnType("integer"); + + b.HasKey("LibrariesID", "ShowsID"); + + b.HasIndex("ShowsID"); + + b.ToTable("LibraryShow"); + }); + + modelBuilder.Entity("CollectionLibrary", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Kyoo.Models.CollectionDE", "Parent") - .WithMany("Links") - .HasForeignKey("ParentID") + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionShow", b => + { + b.HasOne("Kyoo.Models.Collection", null) + .WithMany() + .HasForeignKey("CollectionsID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Child"); + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); - b.Navigation("Parent"); + modelBuilder.Entity("GenreShow", b => + { + b.HasOne("Kyoo.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -525,7 +581,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .WithMany("Episodes") .HasForeignKey("SeasonID"); - b.HasOne("Kyoo.Models.ShowDE", "Show") + b.HasOne("Kyoo.Models.Show", "Show") .WithMany("Episodes") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) @@ -536,42 +592,21 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.GenreLink", b => - { - b.HasOne("Kyoo.Models.GenreDE", "Child") - .WithMany("Links") - .HasForeignKey("ChildID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.ShowDE", "Parent") - .WithMany("GenreLinks") - .HasForeignKey("ParentID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Child"); - - b.Navigation("Parent"); - }); - modelBuilder.Entity("Kyoo.Models.LibraryLink", b => { - b.HasOne("Kyoo.Models.CollectionDE", "Collection") - .WithMany("LibraryLinks") - .HasForeignKey("CollectionID") - .OnDelete(DeleteBehavior.Cascade); + b.HasOne("Kyoo.Models.Collection", "Collection") + .WithMany() + .HasForeignKey("CollectionID"); - b.HasOne("Kyoo.Models.LibraryDE", "Library") - .WithMany("Links") + b.HasOne("Kyoo.Models.Library", "Library") + .WithMany() .HasForeignKey("LibraryID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Kyoo.Models.ShowDE", "Show") - .WithMany("LibraryLinks") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade); + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany() + .HasForeignKey("ShowID"); b.Navigation("Collection"); @@ -603,7 +638,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("SeasonID") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Kyoo.Models.ShowDE", "Show") + b.HasOne("Kyoo.Models.Show", "Show") .WithMany("ExternalIDs") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade); @@ -627,7 +662,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Kyoo.Models.ShowDE", "Show") + b.HasOne("Kyoo.Models.Show", "Show") .WithMany("People") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) @@ -638,28 +673,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.ProviderLink", b => - { - b.HasOne("Kyoo.Models.ProviderID", "Child") - .WithMany() - .HasForeignKey("ChildID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.LibraryDE", "Parent") - .WithMany("ProviderLinks") - .HasForeignKey("ParentID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Child"); - - b.Navigation("Parent"); - }); - modelBuilder.Entity("Kyoo.Models.Season", b => { - b.HasOne("Kyoo.Models.ShowDE", "Show") + b.HasOne("Kyoo.Models.Show", "Show") .WithMany("Seasons") .HasForeignKey("ShowID") .OnDelete(DeleteBehavior.Cascade) @@ -668,10 +684,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Show"); }); - modelBuilder.Entity("Kyoo.Models.ShowDE", b => + modelBuilder.Entity("Kyoo.Models.Show", b => { b.HasOne("Kyoo.Models.Studio", "Studio") - .WithMany() + .WithMany("Shows") .HasForeignKey("StudioID"); b.Navigation("Studio"); @@ -688,11 +704,34 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Episode"); }); - modelBuilder.Entity("Kyoo.Models.CollectionDE", b => + modelBuilder.Entity("LibraryProviderID", b => { - b.Navigation("LibraryLinks"); + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Links"); + b.HasOne("Kyoo.Models.ProviderID", null) + .WithMany() + .HasForeignKey("ProvidersID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryShow", b => + { + b.HasOne("Kyoo.Models.Library", null) + .WithMany() + .HasForeignKey("LibrariesID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", null) + .WithMany() + .HasForeignKey("ShowsID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Kyoo.Models.Episode", b => @@ -702,18 +741,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Tracks"); }); - modelBuilder.Entity("Kyoo.Models.GenreDE", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Kyoo.Models.LibraryDE", b => - { - b.Navigation("Links"); - - b.Navigation("ProviderLinks"); - }); - modelBuilder.Entity("Kyoo.Models.People", b => { b.Navigation("ExternalIDs"); @@ -728,22 +755,21 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("ExternalIDs"); }); - modelBuilder.Entity("Kyoo.Models.ShowDE", b => + modelBuilder.Entity("Kyoo.Models.Show", b => { - b.Navigation("CollectionLinks"); - b.Navigation("Episodes"); b.Navigation("ExternalIDs"); - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - b.Navigation("People"); b.Navigation("Seasons"); }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Navigation("Shows"); + }); #pragma warning restore 612, 618 } } diff --git a/Kyoo/Tasks/CoreTaskHolder.cs b/Kyoo/Tasks/CoreTaskHolder.cs index 5d5948e7..867c83f4 100644 --- a/Kyoo/Tasks/CoreTaskHolder.cs +++ b/Kyoo/Tasks/CoreTaskHolder.cs @@ -11,7 +11,7 @@ namespace Kyoo.Tasks new PluginLoader(), new Crawler(), new MetadataProviderLoader(), - new ReScan(), + // new ReScan(), new ExtractMetadata() }; } diff --git a/Kyoo/Tasks/ReScan.cs b/Kyoo/Tasks/ReScan.cs index 7ab182a7..a4a62786 100644 --- a/Kyoo/Tasks/ReScan.cs +++ b/Kyoo/Tasks/ReScan.cs @@ -1,127 +1,127 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Kyoo.Controllers; -using Kyoo.Models; -using Microsoft.Extensions.DependencyInjection; - -namespace Kyoo.Tasks -{ - public class ReScan: ITask - { - public string Slug => "re-scan"; - public string Name => "ReScan"; - public string Description => "Re download metadata of an item using it's external ids."; - public string HelpMessage => null; - public bool RunOnStartup => false; - public int Priority => 0; - - - private IServiceProvider _serviceProvider; - private IThumbnailsManager _thumbnailsManager; - private IProviderManager _providerManager; - private DatabaseContext _database; - - public async Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null) - { - using IServiceScope serviceScope = serviceProvider.CreateScope(); - _serviceProvider = serviceProvider; - _thumbnailsManager = serviceProvider.GetService(); - _providerManager = serviceProvider.GetService(); - _database = serviceScope.ServiceProvider.GetService(); - - if (arguments == null || !arguments.Contains('/')) - return; - - string slug = arguments.Substring(arguments.IndexOf('/') + 1); - switch (arguments.Substring(0, arguments.IndexOf('/'))) - { - case "show": - await ReScanShow(slug); - break; - case "season": - await ReScanSeason(slug); - break; - } - } - - private async Task ReScanShow(string slug) - { - Show old; - - using (IServiceScope serviceScope = _serviceProvider.CreateScope()) - { - ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); - old = _database.Shows.FirstOrDefault(x => x.Slug == slug); - if (old == null) - return; - Library library = _database.LibraryLinks.First(x => x.Show == old && x.Library != null).Library; - Show edited = await _providerManager.CompleteShow(old, library); - edited.ID = old.ID; - edited.Slug = old.Slug; - edited.Path = old.Path; - await libraryManager.EditShow(edited, true); - await _thumbnailsManager.Validate(edited, true); - } - if (old.Seasons != null) - await Task.WhenAll(old.Seasons.Select(x => ReScanSeason(old, x))); - IEnumerable orphans = old.Episodes.Where(x => x.Season == null).ToList(); - if (orphans.Any()) - await Task.WhenAll(orphans.Select(x => ReScanEpisode(old, x))); - } - - private async Task ReScanSeason(string seasonSlug) - { - string[] infos = seasonSlug.Split('-'); - if (infos.Length != 2 || int.TryParse(infos[1], out int seasonNumber)) - return; - string slug = infos[0]; - Show show = _database.Shows.FirstOrDefault(x => x.Slug == slug); - if (show == null) - return; - Season old = _database.Seasons.FirstOrDefault(x => x.SeasonNumber == seasonNumber && x.Show.ID == show.ID); - if (old == null) - return; - await ReScanSeason(show, old); - } - - private async Task ReScanSeason(Show show, Season old) - { - using (IServiceScope serviceScope = _serviceProvider.CreateScope()) - { - ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); - Library library = _database.LibraryLinks.First(x => x.Show == show && x.Library != null).Library; - Season edited = await _providerManager.GetSeason(show, old.SeasonNumber, library); - edited.ID = old.ID; - await libraryManager.EditSeason(edited, true); - await _thumbnailsManager.Validate(edited, true); - } - if (old.Episodes != null) - await Task.WhenAll(old.Episodes.Select(x => ReScanEpisode(show, x))); - } - - private async Task ReScanEpisode(Show show, Episode old) - { - using IServiceScope serviceScope = _serviceProvider.CreateScope(); - ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); - - Library library = _database.LibraryLinks.First(x => x.Show == show && x.Library != null).Library; - Episode edited = await _providerManager.GetEpisode(show, old.Path, old.SeasonNumber, old.EpisodeNumber, old.AbsoluteNumber, library); - edited.ID = old.ID; - await libraryManager.EditEpisode(edited, true); - await _thumbnailsManager.Validate(edited, true); - } - - public Task> GetPossibleParameters() - { - return Task.FromResult>(null); - } - - public int? Progress() - { - return null; - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.Linq; +// using System.Threading; +// using System.Threading.Tasks; +// using Kyoo.Controllers; +// using Kyoo.Models; +// using Microsoft.Extensions.DependencyInjection; +// +// namespace Kyoo.Tasks +// { +// public class ReScan: ITask +// { +// public string Slug => "re-scan"; +// public string Name => "ReScan"; +// public string Description => "Re download metadata of an item using it's external ids."; +// public string HelpMessage => null; +// public bool RunOnStartup => false; +// public int Priority => 0; +// +// +// private IServiceProvider _serviceProvider; +// private IThumbnailsManager _thumbnailsManager; +// private IProviderManager _providerManager; +// private DatabaseContext _database; +// +// public async Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null) +// { +// using IServiceScope serviceScope = serviceProvider.CreateScope(); +// _serviceProvider = serviceProvider; +// _thumbnailsManager = serviceProvider.GetService(); +// _providerManager = serviceProvider.GetService(); +// _database = serviceScope.ServiceProvider.GetService(); +// +// if (arguments == null || !arguments.Contains('/')) +// return; +// +// string slug = arguments.Substring(arguments.IndexOf('/') + 1); +// switch (arguments.Substring(0, arguments.IndexOf('/'))) +// { +// case "show": +// await ReScanShow(slug); +// break; +// case "season": +// await ReScanSeason(slug); +// break; +// } +// } +// +// private async Task ReScanShow(string slug) +// { +// Show old; +// +// using (IServiceScope serviceScope = _serviceProvider.CreateScope()) +// { +// ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); +// old = _database.Shows.FirstOrDefault(x => x.Slug == slug); +// if (old == null) +// return; +// Library library = _database.LibraryLinks.First(x => x.Show == old && x.Library != null).Library; +// Show edited = await _providerManager.CompleteShow(old, library); +// edited.ID = old.ID; +// edited.Slug = old.Slug; +// edited.Path = old.Path; +// await libraryManager.EditShow(edited, true); +// await _thumbnailsManager.Validate(edited, true); +// } +// if (old.Seasons != null) +// await Task.WhenAll(old.Seasons.Select(x => ReScanSeason(old, x))); +// IEnumerable orphans = old.Episodes.Where(x => x.Season == null).ToList(); +// if (orphans.Any()) +// await Task.WhenAll(orphans.Select(x => ReScanEpisode(old, x))); +// } +// +// private async Task ReScanSeason(string seasonSlug) +// { +// string[] infos = seasonSlug.Split('-'); +// if (infos.Length != 2 || int.TryParse(infos[1], out int seasonNumber)) +// return; +// string slug = infos[0]; +// Show show = _database.Shows.FirstOrDefault(x => x.Slug == slug); +// if (show == null) +// return; +// Season old = _database.Seasons.FirstOrDefault(x => x.SeasonNumber == seasonNumber && x.Show.ID == show.ID); +// if (old == null) +// return; +// await ReScanSeason(show, old); +// } +// +// private async Task ReScanSeason(Show show, Season old) +// { +// using (IServiceScope serviceScope = _serviceProvider.CreateScope()) +// { +// ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); +// Library library = _database.LibraryLinks.First(x => x.Show == show && x.Library != null).Library; +// Season edited = await _providerManager.GetSeason(show, old.SeasonNumber, library); +// edited.ID = old.ID; +// await libraryManager.EditSeason(edited, true); +// await _thumbnailsManager.Validate(edited, true); +// } +// if (old.Episodes != null) +// await Task.WhenAll(old.Episodes.Select(x => ReScanEpisode(show, x))); +// } +// +// private async Task ReScanEpisode(Show show, Episode old) +// { +// using IServiceScope serviceScope = _serviceProvider.CreateScope(); +// ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); +// +// Library library = _database.LibraryLinks.First(x => x.Show == show && x.Library != null).Library; +// Episode edited = await _providerManager.GetEpisode(show, old.Path, old.SeasonNumber, old.EpisodeNumber, old.AbsoluteNumber, library); +// edited.ID = old.ID; +// await libraryManager.EditEpisode(edited, true); +// await _thumbnailsManager.Validate(edited, true); +// } +// +// public Task> GetPossibleParameters() +// { +// return Task.FromResult>(null); +// } +// +// public int? Progress() +// { +// return null; +// } +// } +// } \ No newline at end of file From fef6a93a1d6d78d385dbe89e2c59007fe4bf3a56 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 1 Mar 2021 00:49:03 +0100 Subject: [PATCH 20/54] Removing most of the things needed for a custom many to many --- Kyoo.Common/Utility.cs | 17 +++ Kyoo.CommonAPI/LocalRepository.cs | 112 +----------------- .../Repositories/CollectionRepository.cs | 16 +-- .../Repositories/EpisodeRepository.cs | 2 +- .../Repositories/GenreRepository.cs | 13 +- .../Repositories/LibraryItemRepository.cs | 25 ++-- .../Repositories/LibraryRepository.cs | 31 ++--- .../Repositories/PeopleRepository.cs | 2 +- .../Repositories/ProviderRepository.cs | 2 +- .../Repositories/SeasonRepository.cs | 2 +- .../Repositories/ShowRepository.cs | 45 ++----- .../Repositories/StudioRepository.cs | 2 +- .../Repositories/TrackRepository.cs | 2 +- Kyoo/Models/DatabaseContext.cs | 6 - Kyoo/Models/Links/CollectionLink.cs | 20 ---- Kyoo/Models/Links/GenreLink.cs | 18 --- Kyoo/Models/Links/LibraryLink.cs | 27 ----- Kyoo/Models/Links/ProviderLink.cs | 20 ---- Kyoo/Models/Resources/CollectionDE.cs | 33 ------ Kyoo/Models/Resources/GenreDE.cs | 25 ---- Kyoo/Models/Resources/LibraryDE.cs | 42 ------- Kyoo/Models/Resources/ShowDE.cs | 40 ------- 22 files changed, 70 insertions(+), 432 deletions(-) delete mode 100644 Kyoo/Models/Links/CollectionLink.cs delete mode 100644 Kyoo/Models/Links/GenreLink.cs delete mode 100644 Kyoo/Models/Links/LibraryLink.cs delete mode 100644 Kyoo/Models/Links/ProviderLink.cs delete mode 100644 Kyoo/Models/Resources/CollectionDE.cs delete mode 100644 Kyoo/Models/Resources/GenreDE.cs delete mode 100644 Kyoo/Models/Resources/LibraryDE.cs delete mode 100644 Kyoo/Models/Resources/ShowDE.cs diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index ae13b2f7..423edf1f 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -239,6 +239,23 @@ namespace Kyoo return types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); } + public static async IAsyncEnumerable SelectAsync(this IEnumerable self, Func> mapper) + { + using IEnumerator enumerator = self.GetEnumerator(); + + while (enumerator.MoveNext()) + yield return await mapper(enumerator.Current); + } + + public static async Task> ToListAsync(this IAsyncEnumerable self) + { + List ret = new(); + + await foreach(T i in self) + ret.Add(i); + return ret; + } + public static IEnumerable IfEmpty(this IEnumerable self, Action action) { using IEnumerator enumerator = self.GetEnumerator(); diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 3f2b4cf6..0be49075 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -15,7 +15,7 @@ using Microsoft.EntityFrameworkCore.Metadata; namespace Kyoo.Controllers { - public abstract class LocalRepository + public abstract class LocalRepository : IRepository where T : class, IResource { protected readonly DbContext Database; @@ -53,6 +53,8 @@ namespace Kyoo.Controllers return Database.Set().FirstOrDefaultAsync(predicate); } + public abstract Task> Search(string query); + public virtual Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) @@ -256,112 +258,4 @@ namespace Kyoo.Controllers await Delete(slug); } } - - public abstract class LocalRepository : LocalRepository, IRepository - where T : class, IResource - where TInternal : class, T, new() - { - protected LocalRepository(DbContext database) : base(database) { } - - public new Task Get(int id) - { - return base.Get(id).Cast(); - } - - public new Task Get(string slug) - { - return base.Get(slug).Cast(); - } - - public Task Get(Expression> predicate) - { - return Get(predicate.Convert>()).Cast(); - } - - public abstract Task> Search(string query); - - public virtual Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return ApplyFilters(Database.Set(), where, sort, limit); - } - - protected virtual async Task> ApplyFilters(IQueryable query, - Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - ICollection items = await ApplyFilters(query, - base.Get, - DefaultSort, - where.Convert>(), - sort.To(), - limit); - - return items.ToList(); - } - - public virtual Task GetCount(Expression> where = null) - { - IQueryable query = Database.Set(); - if (where != null) - query = query.Where(where.Convert>()); - return query.CountAsync(); - } - - Task IRepository.Create(T item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - TInternal obj = item as TInternal ?? new TInternal(); - if (!(item is TInternal)) - Utility.Assign(obj, item); - - return Create(obj).Cast() - .Then(x => item.ID = x.ID); - } - - Task IRepository.CreateIfNotExists(T item, bool silentFail) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - TInternal obj = item as TInternal ?? new TInternal(); - if (!(item is TInternal)) - Utility.Assign(obj, item); - - return CreateIfNotExists(obj, silentFail).Cast() - .Then(x => item.ID = x.ID); - } - - public Task Edit(T edited, bool resetOld) - { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - if (edited is TInternal intern) - return Edit(intern, resetOld).Cast(); - TInternal obj = new(); - Utility.Assign(obj, edited); - return base.Edit(obj, resetOld).Cast(); - } - - public abstract override Task Delete([NotNull] TInternal obj); - - Task IRepository.Delete(T obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - if (obj is TInternal intern) - return Delete(intern); - TInternal item = new(); - Utility.Assign(item, obj); - return Delete(item); - } - - public virtual async Task DeleteRange(IEnumerable objs) - { - foreach (T obj in objs) - await ((IRepository)this).Delete(obj); - } - } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 230e79ae..d390e41f 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -8,11 +8,11 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class CollectionRepository : LocalRepository, ICollectionRepository + public class CollectionRepository : LocalRepository, ICollectionRepository { private bool _disposed; private readonly DatabaseContext _database; - protected override Expression> DefaultSort => x => x.Name; + protected override Expression> DefaultSort => x => x.Name; public CollectionRepository(DatabaseContext database) : base(database) { @@ -40,10 +40,10 @@ namespace Kyoo.Controllers return await _database.Collections .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Take(20) - .ToListAsync(); + .ToListAsync(); } - public override async Task Create(CollectionDE obj) + public override async Task Create(Collection obj) { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; @@ -51,18 +51,12 @@ namespace Kyoo.Controllers return obj; } - public override async Task Delete(CollectionDE obj) + public override async Task Delete(Collection obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - if (obj.Links != null) - foreach (CollectionLink link in obj.Links) - _database.Entry(link).State = EntityState.Deleted; - if (obj.LibraryLinks != null) - foreach (LibraryLink link in obj.LibraryLinks) - _database.Entry(link).State = EntityState.Deleted; await _database.SaveChangesAsync(); } } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index a95bfcad..e618c6b9 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -86,7 +86,7 @@ namespace Kyoo.Controllers && x.AbsoluteNumber == absoluteNumber); } - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Episodes .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index 3a2261c6..c9ef602d 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -8,11 +8,11 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class GenreRepository : LocalRepository, IGenreRepository + public class GenreRepository : LocalRepository, IGenreRepository { private bool _disposed; private readonly DatabaseContext _database; - protected override Expression> DefaultSort => x => x.Slug; + protected override Expression> DefaultSort => x => x.Slug; public GenreRepository(DatabaseContext database) : base(database) @@ -41,10 +41,10 @@ namespace Kyoo.Controllers return await _database.Genres .Where(genre => EF.Functions.ILike(genre.Name, $"%{query}%")) .Take(20) - .ToListAsync(); + .ToListAsync(); } - public override async Task Create(GenreDE obj) + public override async Task Create(Genre obj) { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; @@ -52,15 +52,12 @@ namespace Kyoo.Controllers return obj; } - public override async Task Delete(GenreDE obj) + public override async Task Delete(Genre obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - if (obj.Links != null) - foreach (GenreLink link in obj.Links) - _database.Entry(link).State = EntityState.Deleted; await _database.SaveChangesAsync(); } } diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 0119409c..b8c5cc17 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -71,7 +71,7 @@ namespace Kyoo.Controllers private IQueryable ItemsQuery => _database.Shows - // .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) + .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) .Select(LibraryItem.FromShow) .Concat(_database.Collections .Select(LibraryItem.FromCollection)); @@ -114,18 +114,17 @@ namespace Kyoo.Controllers public override Task Delete(LibraryItem obj) => throw new InvalidOperationException(); private IQueryable LibraryRelatedQuery(Expression> selector) - => throw new NotImplementedException(); - // => _database.LibraryLinks - // .Where(selector) - // .Select(x => x.Show) - // .Where(x => x != null) - // .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) - // .Select(LibraryItem.FromShow) - // .Concat(_database.LibraryLinks - // .Where(selector) - // .Select(x => x.Collection) - // .Where(x => x != null) - // .Select(LibraryItem.FromCollection)); + => _database.LibraryLinks + .Where(selector) + .Select(x => x.Show) + .Where(x => x != null) + .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) + .Select(LibraryItem.FromShow) + .Concat(_database.LibraryLinks + .Where(selector) + .Select(x => x.Collection) + .Where(x => x != null) + .Select(LibraryItem.FromCollection)); public async Task> GetFromLibrary(int id, Expression> where = null, diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 86cdee90..f49691a9 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -10,12 +10,12 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { - public class LibraryRepository : LocalRepository, ILibraryRepository + public class LibraryRepository : LocalRepository, ILibraryRepository { private bool _disposed; private readonly DatabaseContext _database; private readonly IProviderRepository _providers; - protected override Expression> DefaultSort => x => x.ID; + protected override Expression> DefaultSort => x => x.ID; public LibraryRepository(DatabaseContext database, IProviderRepository providers) @@ -48,22 +48,18 @@ namespace Kyoo.Controllers return await _database.Libraries .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Take(20) - .ToListAsync(); + .ToListAsync(); } - public override async Task Create(LibraryDE obj) + public override async Task Create(Library obj) { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - if (obj.ProviderLinks != null) - foreach (ProviderLink entry in obj.ProviderLinks) - _database.Entry(entry).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); return obj; } - protected override async Task Validate(LibraryDE resource) + protected override async Task Validate(Library resource) { if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("The library's slug must be set and not empty"); @@ -73,25 +69,18 @@ namespace Kyoo.Controllers throw new ArgumentException("The library should have a least one path."); await base.Validate(resource); - - if (resource.ProviderLinks != null) - foreach (ProviderLink link in resource.ProviderLinks) - if (ShouldValidate(link)) - link.Child = await _providers.CreateIfNotExists(link.Child, true); + + resource.Providers = await resource.Providers + .SelectAsync(x => _providers.CreateIfNotExists(x, true)) + .ToListAsync(); } - public override async Task Delete(LibraryDE obj) + public override async Task Delete(Library obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - if (obj.ProviderLinks != null) - foreach (ProviderLink entry in obj.ProviderLinks) - _database.Entry(entry).State = EntityState.Deleted; - if (obj.Links != null) - foreach (LibraryLink entry in obj.Links) - _database.Entry(entry).State = EntityState.Deleted; await _database.SaveChangesAsync(); } } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index f537e59b..0deecccf 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -49,7 +49,7 @@ namespace Kyoo.Controllers await _shows.Value.DisposeAsync(); } - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.People .Where(people => EF.Functions.ILike(people.Name, $"%{query}%")) diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 9dd112c0..1d229921 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -19,7 +19,7 @@ namespace Kyoo.Controllers _database = database; } - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Providers .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 97bd8319..5fa057be 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -74,7 +74,7 @@ namespace Kyoo.Controllers && x.SeasonNumber == seasonNumber); } - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Seasons .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 339ade96..468d6404 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { - public class ShowRepository : LocalRepository, IShowRepository + public class ShowRepository : LocalRepository, IShowRepository { private bool _disposed; private readonly DatabaseContext _database; @@ -19,7 +19,7 @@ namespace Kyoo.Controllers private readonly IProviderRepository _providers; private readonly Lazy _seasons; private readonly Lazy _episodes; - protected override Expression> DefaultSort => x => x.Title; + protected override Expression> DefaultSort => x => x.Title; public ShowRepository(DatabaseContext database, IStudioRepository studios, @@ -81,20 +81,10 @@ namespace Kyoo.Controllers .ToListAsync(); } - public override async Task Create(ShowDE obj) + public override async Task Create(Show obj) { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - - if (obj.GenreLinks != null) - { - foreach (GenreLink entry in obj.GenreLinks) - { - if (!(entry.Child is GenreDE)) - entry.Child = new GenreDE(entry.Child); - _database.Entry(entry).State = EntityState.Added; - } - } if (obj.People != null) foreach (PeopleRole entry in obj.People) @@ -107,17 +97,16 @@ namespace Kyoo.Controllers return obj; } - protected override async Task Validate(ShowDE resource) + protected override async Task Validate(Show resource) { await base.Validate(resource); if (ShouldValidate(resource.Studio)) resource.Studio = await _studios.CreateIfNotExists(resource.Studio, true); - if (resource.GenreLinks != null) - foreach (GenreLink link in resource.GenreLinks) - if (ShouldValidate(link)) - link.Child = await _genres.CreateIfNotExists(link.Child, true); + resource.Genres = await resource.Genres + .SelectAsync(x => _genres.CreateIfNotExists(x, true)) + .ToListAsync(); if (resource.People != null) foreach (PeopleRole link in resource.People) @@ -134,32 +123,30 @@ namespace Kyoo.Controllers { if (collectionID != null) { - // await _database.CollectionLinks.AddAsync(new CollectionLink {ParentID = collectionID.Value, ChildID = showID}); + + await _database.CollectionLinks.AddAsync(new CollectionLink {ParentID = collectionID.Value, ChildID = showID}); await _database.SaveIfNoDuplicates(); } if (libraryID != null) { - // await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, ShowID = showID}); + await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, ShowID = showID}); await _database.SaveIfNoDuplicates(); } if (libraryID != null && collectionID != null) { - // await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, CollectionID = collectionID.Value}); + await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, CollectionID = collectionID.Value}); await _database.SaveIfNoDuplicates(); } } - public override async Task Delete(ShowDE obj) + public override async Task Delete(Show obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - if (obj.GenreLinks != null) - foreach (GenreLink entry in obj.GenreLinks) - _database.Entry(entry).State = EntityState.Deleted; if (obj.People != null) foreach (PeopleRole entry in obj.People) @@ -168,14 +155,6 @@ namespace Kyoo.Controllers if (obj.ExternalIDs != null) foreach (MetadataID entry in obj.ExternalIDs) _database.Entry(entry).State = EntityState.Deleted; - - if (obj.CollectionLinks != null) - foreach (CollectionLink entry in obj.CollectionLinks) - _database.Entry(entry).State = EntityState.Deleted; - - if (obj.LibraryLinks != null) - foreach (LibraryLink entry in obj.LibraryLinks) - _database.Entry(entry).State = EntityState.Deleted; await _database.SaveChangesAsync(); diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index eebf0fdc..d020de24 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -19,7 +19,7 @@ namespace Kyoo.Controllers _database = database; } - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Studios .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index ee5366aa..1008dab7 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -73,7 +73,7 @@ namespace Kyoo.Controllers && x.IsForced == forced); } - public Task> Search(string query) + public override Task> Search(string query) { throw new InvalidOperationException("Tracks do not support the search method."); } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index dc10c390..bfb049df 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -119,12 +119,6 @@ namespace Kyoo modelBuilder.Entity() .HasIndex(x => new {x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber}) .IsUnique(); - modelBuilder.Entity() - .HasIndex(x => new {x.LibraryID, x.ShowID}) - .IsUnique(); - modelBuilder.Entity() - .HasIndex(x => new {x.LibraryID, x.CollectionID}) - .IsUnique(); } public override int SaveChanges() diff --git a/Kyoo/Models/Links/CollectionLink.cs b/Kyoo/Models/Links/CollectionLink.cs deleted file mode 100644 index 9af6d1df..00000000 --- a/Kyoo/Models/Links/CollectionLink.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Kyoo.Models -{ - public class CollectionLink : IResourceLink - { - public int ParentID { get; set; } - public virtual Collection Parent { get; set; } - public int ChildID { get; set; } - public virtual Show Child { get; set; } - - public CollectionLink() { } - - public CollectionLink(Collection parent, Show child) - { - Parent = parent; - ParentID = parent.ID; - Child = child; - ChildID = child.ID; - } - } -} \ No newline at end of file diff --git a/Kyoo/Models/Links/GenreLink.cs b/Kyoo/Models/Links/GenreLink.cs deleted file mode 100644 index 09dbde70..00000000 --- a/Kyoo/Models/Links/GenreLink.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Kyoo.Models -{ - public class GenreLink : IResourceLink - { - public int ParentID { get; set; } - public virtual Show Parent { get; set; } - public int ChildID { get; set; } - public virtual Genre Child { get; set; } - - public GenreLink() {} - - public GenreLink(Show parent, Genre child) - { - Parent = parent; - Child = child; - } - } -} \ No newline at end of file diff --git a/Kyoo/Models/Links/LibraryLink.cs b/Kyoo/Models/Links/LibraryLink.cs deleted file mode 100644 index 22120348..00000000 --- a/Kyoo/Models/Links/LibraryLink.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Kyoo.Models -{ - public class LibraryLink - { - public int ID { get; set; } - public int LibraryID { get; set; } - public virtual Library Library { get; set; } - public int? ShowID { get; set; } - public virtual Show Show { get; set; } - public int? CollectionID { get; set; } - public virtual Collection Collection { get; set; } - - public LibraryLink() { } - - public LibraryLink(Library library, Show show) - { - Library = library; - Show = show; - } - - public LibraryLink(Library library, Collection collection) - { - Library = library; - Collection = collection; - } - } -} \ No newline at end of file diff --git a/Kyoo/Models/Links/ProviderLink.cs b/Kyoo/Models/Links/ProviderLink.cs deleted file mode 100644 index 584af900..00000000 --- a/Kyoo/Models/Links/ProviderLink.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Newtonsoft.Json; - -namespace Kyoo.Models -{ - public class ProviderLink : IResourceLink - { - [JsonIgnore] public int ParentID { get; set; } - [JsonIgnore] public virtual Library Parent { get; set; } - [JsonIgnore] public int ChildID { get; set; } - [JsonIgnore] public virtual ProviderID Child { get; set; } - - public ProviderLink() { } - - public ProviderLink(ProviderID child, Library parent) - { - Child = child; - Parent = parent; - } - } -} \ No newline at end of file diff --git a/Kyoo/Models/Resources/CollectionDE.cs b/Kyoo/Models/Resources/CollectionDE.cs deleted file mode 100644 index 10a53852..00000000 --- a/Kyoo/Models/Resources/CollectionDE.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Kyoo.Models.Attributes; - -namespace Kyoo.Models -{ - public class CollectionDE : Collection - { - [SerializeIgnore] [NotMergable] public virtual ICollection Links { get; set; } - [ExpressionRewrite(nameof(Links), nameof(CollectionLink.Child))] - public override ICollection Shows - { - get => Links?.Select(x => x.Child).ToList(); - set => Links = value?.Select(x => new CollectionLink(this, x)).ToList(); - } - - [SerializeIgnore] [NotMergable] public virtual ICollection LibraryLinks { get; set; } - - [ExpressionRewrite(nameof(LibraryLinks), nameof(GenreLink.Child))] - public override ICollection Libraries - { - get => LibraryLinks?.Select(x => x.Library).ToList(); - set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)).ToList(); - } - - public CollectionDE() {} - - public CollectionDE(Collection collection) - { - Utility.Assign(this, collection); - } - } -} \ No newline at end of file diff --git a/Kyoo/Models/Resources/GenreDE.cs b/Kyoo/Models/Resources/GenreDE.cs deleted file mode 100644 index 0a77198e..00000000 --- a/Kyoo/Models/Resources/GenreDE.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Kyoo.Models.Attributes; - -namespace Kyoo.Models -{ - public class GenreDE : Genre - { - [SerializeIgnore] [NotMergable] public virtual ICollection Links { get; set; } - - [ExpressionRewrite(nameof(Links), nameof(GenreLink.Child))] - [SerializeIgnore] [NotMergable] public override ICollection Shows - { - get => Links?.Select(x => x.Parent).ToList(); - set => Links = value?.Select(x => new GenreLink(x, this)).ToList(); - } - - public GenreDE() {} - - public GenreDE(Genre item) - { - Utility.Assign(this, item); - } - } -} \ No newline at end of file diff --git a/Kyoo/Models/Resources/LibraryDE.cs b/Kyoo/Models/Resources/LibraryDE.cs deleted file mode 100644 index 47c94c18..00000000 --- a/Kyoo/Models/Resources/LibraryDE.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Kyoo.Models.Attributes; - -namespace Kyoo.Models -{ - public class LibraryDE : Library - { - [EditableRelation] [SerializeIgnore] [NotMergable] public virtual ICollection ProviderLinks { get; set; } - [ExpressionRewrite(nameof(ProviderLinks), nameof(ProviderLink.Child))] - public override ICollection Providers - { - get => ProviderLinks?.Select(x => x.Child).ToList(); - set => ProviderLinks = value?.Select(x => new ProviderLink(x, this)).ToList(); - } - - [SerializeIgnore] [NotMergable] public virtual ICollection Links { get; set; } - [ExpressionRewrite(nameof(Links), nameof(LibraryLink.Show))] - public override ICollection Shows - { - get => Links?.Where(x => x.Show != null).Select(x => x.Show).ToList(); - set => Links = Utility.MergeLists( - value?.Select(x => new LibraryLink(this, x)), - Links?.Where(x => x.Show == null))?.ToList(); - } - [ExpressionRewrite(nameof(Links), nameof(LibraryLink.Collection))] - public override ICollection Collections - { - get => Links?.Where(x => x.Collection != null).Select(x => x.Collection).ToList(); - set => Links = Utility.MergeLists( - value?.Select(x => new LibraryLink(this, x)), - Links?.Where(x => x.Collection == null))?.ToList(); - } - - public LibraryDE() {} - - public LibraryDE(Library item) - { - Utility.Assign(this, item); - } - } -} \ No newline at end of file diff --git a/Kyoo/Models/Resources/ShowDE.cs b/Kyoo/Models/Resources/ShowDE.cs deleted file mode 100644 index 7491effe..00000000 --- a/Kyoo/Models/Resources/ShowDE.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Kyoo.Models.Attributes; - -namespace Kyoo.Models -{ - public class ShowDE : Show - { - [EditableRelation] [SerializeIgnore] [NotMergable] public virtual ICollection GenreLinks { get; set; } - [ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Child))] - public override ICollection Genres - { - get => GenreLinks?.Select(x => x.Child).ToList(); - set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList(); - } - - [SerializeIgnore] [NotMergable] public virtual ICollection LibraryLinks { get; set; } - [ExpressionRewrite(nameof(LibraryLinks), nameof(LibraryLink.Library))] - public override ICollection Libraries - { - get => LibraryLinks?.Select(x => x.Library).ToList(); - set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)).ToList(); - } - - [SerializeIgnore] [NotMergable] public virtual ICollection CollectionLinks { get; set; } - [ExpressionRewrite(nameof(CollectionLinks), nameof(CollectionLink.Parent))] - public override ICollection Collections - { - get => CollectionLinks?.Select(x => x.Parent).ToList(); - set => CollectionLinks = value?.Select(x => new CollectionLink(x, this)).ToList(); - } - - public ShowDE() {} - - public ShowDE(Show show) - { - Utility.Assign(this, show); - } - } -} \ No newline at end of file From b8fcc5b7cfe7c4ea246752915b30207dea542652 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 2 Mar 2021 19:07:40 +0100 Subject: [PATCH 21/54] Reworking show's link creations --- .../Repositories/LibraryItemRepository.cs | 22 ++++++++---------- .../Repositories/ShowRepository.cs | 23 ++++++++++++------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index b8c5cc17..b02b3bea 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -71,7 +71,7 @@ namespace Kyoo.Controllers private IQueryable ItemsQuery => _database.Shows - .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) + .Where(x => !x.Collections.Any()) .Select(LibraryItem.FromShow) .Concat(_database.Collections .Select(LibraryItem.FromCollection)); @@ -91,7 +91,7 @@ namespace Kyoo.Controllers return query.CountAsync(); } - public async Task> Search(string query) + public override async Task> Search(string query) { return await ItemsQuery .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) @@ -113,17 +113,15 @@ namespace Kyoo.Controllers public override Task Delete(string slug) => throw new InvalidOperationException(); public override Task Delete(LibraryItem obj) => throw new InvalidOperationException(); - private IQueryable LibraryRelatedQuery(Expression> selector) - => _database.LibraryLinks + private IQueryable LibraryRelatedQuery(Expression> selector) + => _database.Libraries .Where(selector) - .Select(x => x.Show) - .Where(x => x != null) - .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID)) + .SelectMany(x => x.Shows) + .Where(x => !x.Collections.Any()) .Select(LibraryItem.FromShow) - .Concat(_database.LibraryLinks + .Concat(_database.Libraries .Where(selector) - .Select(x => x.Collection) - .Where(x => x != null) + .SelectMany(x => x.Collections) .Select(LibraryItem.FromCollection)); public async Task> GetFromLibrary(int id, @@ -131,7 +129,7 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.LibraryID == id), + ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.ID == id), where, sort, limit); @@ -145,7 +143,7 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.Library.Slug == slug), + ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.Slug == slug), where, sort, limit); diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 468d6404..8e41dd59 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -78,7 +79,7 @@ namespace Kyoo.Controllers || EF.Functions.ILike(x.Slug, query) /*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE. .Take(20) - .ToListAsync(); + .ToListAsync(); } public override async Task Create(Show obj) @@ -121,22 +122,28 @@ namespace Kyoo.Controllers public async Task AddShowLink(int showID, int? libraryID, int? collectionID) { + Show show = await Get(showID); if (collectionID != null) { - - await _database.CollectionLinks.AddAsync(new CollectionLink {ParentID = collectionID.Value, ChildID = showID}); - await _database.SaveIfNoDuplicates(); + show.Collections ??= new List(); + show.Collections.Add(new Collection {ID = collectionID.Value}); + await _database.SaveChangesAsync(); } if (libraryID != null) { - await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, ShowID = showID}); - await _database.SaveIfNoDuplicates(); + show.Libraries ??= new List(); + show.Libraries.Add(new Library {ID = libraryID.Value}); + await _database.SaveChangesAsync(); } if (libraryID != null && collectionID != null) { - await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, CollectionID = collectionID.Value}); - await _database.SaveIfNoDuplicates(); + Library library = await _database.Libraries.FirstOrDefaultAsync(x => x.ID == libraryID.Value); + if (library == null) + throw new ItemNotFound($"No library found with the ID {libraryID.Value}"); + library.Collections ??= new List(); + library.Collections.Add(new Collection {ID = collectionID.Value}); + await _database.SaveChangesAsync(); } } From 76cdbc77a3de91aa2fb39969dd02c8c9dc4154d2 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 6 Mar 2021 00:53:21 +0100 Subject: [PATCH 22/54] Using links in the common objects --- .../Implementations/LibraryManager.cs | 4 + Kyoo.Common/Kyoo.Common.csproj | 2 + Kyoo.Common/Models/Link.cs | 23 + Kyoo.Common/Models/Resources/Collection.cs | 5 + Kyoo.Common/Models/Resources/Genre.cs | 7 +- Kyoo.Common/Models/Resources/IResource.cs | 33 - Kyoo.Common/Models/Resources/Library.cs | 6 + Kyoo.Common/Models/Resources/ProviderID.cs | 8 +- Kyoo.Common/Models/Resources/Show.cs | 7 + Kyoo.Common/Utility.cs | 41 +- Kyoo/Controllers/ProviderManager.cs | 2 +- .../Repositories/LibraryRepository.cs | 15 +- .../Repositories/ShowRepository.cs | 43 +- Kyoo/Models/DatabaseContext.cs | 79 +- .../20210216205007_Initial.Designer.cs | 985 ------------------ .../20210216205007_Initial.cs | 658 ------------ .../ConfigurationDbContextModelSnapshot.cs | 983 ----------------- .../20210228232014_Initial.Designer.cs | 778 -------------- .../Internal/20210228232014_Initial.cs | 662 ------------ .../Internal/DatabaseContextModelSnapshot.cs | 776 -------------- Kyoo/Tasks/Crawler.cs | 1 + Kyoo/appsettings.json | 2 +- transcoder | 2 +- 23 files changed, 191 insertions(+), 4931 deletions(-) create mode 100644 Kyoo.Common/Models/Link.cs delete mode 100644 Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.Designer.cs delete mode 100644 Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.cs delete mode 100644 Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs delete mode 100644 Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.Designer.cs delete mode 100644 Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.cs delete mode 100644 Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 18159706..fee5a457 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -349,6 +349,10 @@ namespace Kyoo.Controllers (People p, nameof(People.Roles)) => PeopleRepository .GetFromPeople(obj.ID) .Then(x => p.Roles = x), + + (ProviderID p, nameof(ProviderID.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 {member} of {obj.Slug}.") }; diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index ff7ef1e2..59d84b4b 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -16,6 +16,8 @@ true snupkg default + + ENABLE_INTERNAL_LINKS diff --git a/Kyoo.Common/Models/Link.cs b/Kyoo.Common/Models/Link.cs new file mode 100644 index 00000000..8dee75bb --- /dev/null +++ b/Kyoo.Common/Models/Link.cs @@ -0,0 +1,23 @@ +using System; +using System.Linq.Expressions; + +namespace Kyoo.Models +{ + public class Link + where T1 : class, IResource + where T2 : class, IResource + { + public static Expression, object>> PrimaryKey + { + get + { + return x => new {LibraryID = x.FirstID, ProviderID = x.SecondID}; + } + } + + public int FirstID { get; set; } + public virtual T1 First { get; set; } + public int SecondID { get; set; } + public virtual T2 Second { get; set; } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 34a41f7a..936620c9 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -13,6 +13,11 @@ namespace Kyoo.Models [LoadableRelation] public virtual ICollection Shows { get; set; } [LoadableRelation] public virtual ICollection Libraries { get; set; } +#if ENABLE_INTERNAL_LINKS + [SerializeIgnore] public virtual ICollection> ShowLinks { get; set; } + [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } +#endif + public Collection() { } public Collection(string slug, string name, string overview, string poster) diff --git a/Kyoo.Common/Models/Resources/Genre.cs b/Kyoo.Common/Models/Resources/Genre.cs index 26fbe5a4..cd6086df 100644 --- a/Kyoo.Common/Models/Resources/Genre.cs +++ b/Kyoo.Common/Models/Resources/Genre.cs @@ -10,7 +10,12 @@ namespace Kyoo.Models public string Name { get; set; } [LoadableRelation] public virtual ICollection Shows { get; set; } - + +#if ENABLE_INTERNAL_LINKS + [SerializeIgnore] public virtual ICollection> ShowLinks { get; set; } +#endif + + public Genre() {} public Genre(string name) diff --git a/Kyoo.Common/Models/Resources/IResource.cs b/Kyoo.Common/Models/Resources/IResource.cs index 83806ff5..297f3b1d 100644 --- a/Kyoo.Common/Models/Resources/IResource.cs +++ b/Kyoo.Common/Models/Resources/IResource.cs @@ -9,16 +9,6 @@ namespace Kyoo.Models public string Slug { get; } } - public interface IResourceLink - where T : IResource - where T2 : IResource - { - public T Parent { get; } - public int ParentID { get; } - public T2 Child { get; } - public int ChildID { get; } - } - public class ResourceComparer : IEqualityComparer where T : IResource { public bool Equals(T x, T y) @@ -37,27 +27,4 @@ namespace Kyoo.Models return HashCode.Combine(obj.ID, obj.Slug); } } - - public class LinkComparer : IEqualityComparer - where T : IResourceLink - where T1 : IResource - where T2 : IResource - { - public bool Equals(T x, T y) - { - if (ReferenceEquals(x, y)) - return true; - if (ReferenceEquals(x, null)) - return false; - if (ReferenceEquals(y, null)) - return false; - return Utility.LinkEquals(x.Parent, x.ParentID, y.Parent, y.ParentID) - && Utility.LinkEquals(x.Child, x.ChildID, y.Child, y.ChildID); - } - - public int GetHashCode(T obj) - { - return HashCode.Combine(obj.Parent, obj.ParentID, obj.Child, obj.ChildID); - } - } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index 54f9ac89..c1e17b56 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -16,6 +16,12 @@ namespace Kyoo.Models [LoadableRelation] public virtual ICollection Shows { get; set; } [LoadableRelation] public virtual ICollection Collections { get; set; } +#if ENABLE_INTERNAL_LINKS + [SerializeIgnore] public virtual ICollection> ProviderLinks { get; set; } + [SerializeIgnore] public virtual ICollection> ShowLinks { get; set; } + [SerializeIgnore] public virtual ICollection> CollectionLinks { get; set; } +#endif + public Library() { } public Library(string slug, string name, IEnumerable paths, IEnumerable providers) diff --git a/Kyoo.Common/Models/Resources/ProviderID.cs b/Kyoo.Common/Models/Resources/ProviderID.cs index bb37b395..a874d09e 100644 --- a/Kyoo.Common/Models/Resources/ProviderID.cs +++ b/Kyoo.Common/Models/Resources/ProviderID.cs @@ -10,8 +10,12 @@ namespace Kyoo.Models public string Name { get; set; } public string Logo { get; set; } - [LoadableRelation] public ICollection Libraries { get; set; } - + [LoadableRelation] public virtual ICollection Libraries { get; set; } + +#if ENABLE_INTERNAL_LINKS + [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } +#endif + public ProviderID() { } public ProviderID(string name, string logo) diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index c4feb24c..5eb50eed 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -35,6 +35,13 @@ namespace Kyoo.Models [LoadableRelation] public virtual ICollection Episodes { get; set; } [LoadableRelation] public virtual ICollection Libraries { get; set; } [LoadableRelation] public virtual ICollection Collections { get; set; } + +#if ENABLE_INTERNAL_LINKS + [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } + [SerializeIgnore] public virtual ICollection> CollectionLinks { get; set; } + [SerializeIgnore] public virtual ICollection> GenreLinks { get; set; } +#endif + public Show() { } diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 423edf1f..688e4d54 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -239,16 +239,25 @@ namespace Kyoo return types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); } - public static async IAsyncEnumerable SelectAsync(this IEnumerable self, Func> mapper) + public static async IAsyncEnumerable SelectAsync([NotNull] this IEnumerable self, + [NotNull] Func> mapper) { + if (self == null) + throw new ArgumentNullException(nameof(self)); + if (mapper == null) + throw new ArgumentNullException(nameof(mapper)); + using IEnumerator enumerator = self.GetEnumerator(); while (enumerator.MoveNext()) yield return await mapper(enumerator.Current); } - public static async Task> ToListAsync(this IAsyncEnumerable self) + public static async Task> ToListAsync([NotNull] this IAsyncEnumerable self) { + if (self == null) + throw new ArgumentNullException(nameof(self)); + List ret = new(); await foreach(T i in self) @@ -482,11 +491,14 @@ namespace Kyoo Type type = GetEnumerableType(eno); if (typeof(IResource).IsAssignableFrom(type)) return ResourceEquals(eno.Cast(), ens.Cast()); - Type genericDefinition = GetGenericDefinition(type, typeof(IResourceLink<,>)); - if (genericDefinition == null) - return RunGenericMethod(typeof(Enumerable), "SequenceEqual", type, first, second); - Type[] types = genericDefinition.GetGenericArguments().Prepend(type).ToArray(); - return RunGenericMethod(typeof(Utility), "LinkEquals", types, eno, ens); + return RunGenericMethod(typeof(Enumerable), "SequenceEqual", type, first, second); + } + + public static void Test() + { + #if INTERNAL_LINKS + Console.WriteLine("Lib test"); +#endif } public static bool ResourceEquals([CanBeNull] T first, [CanBeNull] T second) @@ -522,20 +534,7 @@ namespace Kyoo return true; return firstID == secondID; } - - public static bool LinkEquals([CanBeNull] IEnumerable first, [CanBeNull] IEnumerable second) - where T : IResourceLink - where T1 : IResource - where T2 : IResource - { - if (ReferenceEquals(first, second)) - return true; - if (first == null || second == null) - return false; - return first.SequenceEqual(second, new LinkComparer()); - } - - + public static Expression Convert([CanBeNull] this Expression expr) where T : Delegate { diff --git a/Kyoo/Controllers/ProviderManager.cs b/Kyoo/Controllers/ProviderManager.cs index 1efea8e5..b2795c85 100644 --- a/Kyoo/Controllers/ProviderManager.cs +++ b/Kyoo/Controllers/ProviderManager.cs @@ -18,7 +18,7 @@ namespace Kyoo.Controllers private async Task GetMetadata(Func> providerCall, Library library, string what) where T : new() { - T ret = new T(); + T ret = new(); IEnumerable providers = library?.Providers .Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug)) diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index f49691a9..f91231ad 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers @@ -54,7 +55,10 @@ namespace Kyoo.Controllers public override async Task Create(Library obj) { await base.Create(obj); - _database.Entry(obj).State = EntityState.Added; + // _database.Entry(obj).State = EntityState.Added; + // _database.Entry(obj).Collection(x => x.Providers).IsModified = true; + // _database.Add(obj); + // var a = EF.Property>>(obj, nameof(LibraryDE.LibraryLinks)); await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); return obj; } @@ -70,9 +74,12 @@ namespace Kyoo.Controllers await base.Validate(resource); - resource.Providers = await resource.Providers - .SelectAsync(x => _providers.CreateIfNotExists(x, true)) - .ToListAsync(); + if (resource.Providers != null) + { + resource.Providers = await resource.Providers + .SelectAsync(x => _providers.CreateIfNotExists(x, true)) + .ToListAsync(); + } } public override async Task Delete(Library obj) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 8e41dd59..14a9e904 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -104,10 +104,13 @@ namespace Kyoo.Controllers if (ShouldValidate(resource.Studio)) resource.Studio = await _studios.CreateIfNotExists(resource.Studio, true); - - resource.Genres = await resource.Genres - .SelectAsync(x => _genres.CreateIfNotExists(x, true)) - .ToListAsync(); + + if (resource.Genres != null) + { + resource.Genres = await resource.Genres + .SelectAsync(x => _genres.CreateIfNotExists(x, true)) + .ToListAsync(); + } if (resource.People != null) foreach (PeopleRole link in resource.People) @@ -125,25 +128,29 @@ namespace Kyoo.Controllers Show show = await Get(showID); if (collectionID != null) { + Collection collection = _database.GetTemporaryObject(new Collection {ID = collectionID.Value}); + show.Collections ??= new List(); - show.Collections.Add(new Collection {ID = collectionID.Value}); - await _database.SaveChangesAsync(); + show.Collections.Add(collection); + await _database.SaveIfNoDuplicates(); + + if (libraryID != null) + { + Library library = await _database.Libraries.FirstOrDefaultAsync(x => x.ID == libraryID.Value); + if (library == null) + throw new ItemNotFound($"No library found with the ID {libraryID.Value}"); + library.Collections ??= new List(); + library.Collections.Add(collection); + await _database.SaveIfNoDuplicates(); + } } if (libraryID != null) { + Library library = _database.GetTemporaryObject(new Library {ID = libraryID.Value}); + show.Libraries ??= new List(); - show.Libraries.Add(new Library {ID = libraryID.Value}); - await _database.SaveChangesAsync(); - } - - if (libraryID != null && collectionID != null) - { - Library library = await _database.Libraries.FirstOrDefaultAsync(x => x.ID == libraryID.Value); - if (library == null) - throw new ItemNotFound($"No library found with the ID {libraryID.Value}"); - library.Collections ??= new List(); - library.Collections.Add(new Collection {ID = collectionID.Value}); - await _database.SaveChangesAsync(); + show.Libraries.Add(library); + await _database.SaveIfNoDuplicates(); } } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index bfb049df..423bd3e7 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -10,8 +10,6 @@ using Npgsql; namespace Kyoo { - //TODO disable lazy loading a provide a LoadAsync method in the library manager. - public class DatabaseContext : DbContext { public DatabaseContext(DbContextOptions options) : base(options) { } @@ -28,6 +26,7 @@ namespace Kyoo public DbSet Providers { get; set; } public DbSet MetadataIds { get; set; } + // TODO Many to many with UsingEntity for this. public DbSet PeopleRoles { get; set; } @@ -46,11 +45,6 @@ namespace Kyoo modelBuilder.HasPostgresEnum(); modelBuilder.HasPostgresEnum(); - modelBuilder.Ignore(); - modelBuilder.Ignore(); - modelBuilder.Ignore(); - modelBuilder.Ignore(); - modelBuilder.Entity() .Property(x => x.Paths) .HasColumnType("text[]"); @@ -67,6 +61,67 @@ namespace Kyoo .Property(t => t.IsForced) .ValueGeneratedNever(); + modelBuilder.Entity() + .HasMany(x => x.Libraries) + .WithMany(x => x.Providers) + .UsingEntity>( + y => y + .HasOne(x => x.First) + .WithMany(x => x.ProviderLinks), + y => y + .HasOne(x => x.Second) + .WithMany(x => x.LibraryLinks), + y => y.HasKey(x => Link.PrimaryKey)); + + modelBuilder.Entity() + .HasMany(x => x.Libraries) + .WithMany(x => x.Collections) + .UsingEntity>( + y => y + .HasOne(x => x.First) + .WithMany(x => x.CollectionLinks), + y => y + .HasOne(x => x.Second) + .WithMany(x => x.LibraryLinks), + y => y.HasKey(x => Link.PrimaryKey)); + + modelBuilder.Entity() + .HasMany(x => x.Libraries) + .WithMany(x => x.Shows) + .UsingEntity>( + y => y + .HasOne(x => x.First) + .WithMany(x => x.ShowLinks), + y => y + .HasOne(x => x.Second) + .WithMany(x => x.LibraryLinks), + y => y.HasKey(x => Link.PrimaryKey)); + + modelBuilder.Entity() + .HasMany(x => x.Collections) + .WithMany(x => x.Shows) + .UsingEntity>( + y => y + .HasOne(x => x.First) + .WithMany(x => x.ShowLinks), + y => y + .HasOne(x => x.Second) + .WithMany(x => x.CollectionLinks), + y => y.HasKey(x => Link.PrimaryKey)); + + modelBuilder.Entity() + .HasMany(x => x.Shows) + .WithMany(x => x.Genres) + .UsingEntity>( + y => y + .HasOne(x => x.First) + .WithMany(x => x.GenreLinks), + y => y + .HasOne(x => x.Second) + .WithMany(x => x.ShowLinks), + y => y.HasKey(x => Link.PrimaryKey)); + + modelBuilder.Entity() .HasOne(x => x.Show) .WithMany(x => x.ExternalIDs) @@ -121,6 +176,16 @@ namespace Kyoo .IsUnique(); } + public T GetTemporaryObject(T model) + where T : class, IResource + { + T tmp = Set().Local.FirstOrDefault(x => x.ID == model.ID); + if (tmp != null) + return tmp; + Entry(model).State = EntityState.Unchanged; + return model; + } + public override int SaveChanges() { try diff --git a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.Designer.cs deleted file mode 100644 index 87d6032d..00000000 --- a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.Designer.cs +++ /dev/null @@ -1,985 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration -{ - [DbContext(typeof(ConfigurationDbContext))] - [Migration("20210216205007_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.3") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AllowedAccessTokenSigningAlgorithms") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp without time zone"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp without time zone"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiResources"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceScopes"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("Expiration") - .HasColumnType("timestamp without time zone"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceSecrets"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiScopes"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.ToTable("ApiScopeClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.ToTable("ApiScopeProperties"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenType") - .HasColumnType("integer"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("boolean"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("boolean"); - - b.Property("AllowRememberConsent") - .HasColumnType("boolean"); - - b.Property("AllowedIdentityTokenSigningAlgorithms") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("boolean"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("boolean"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("integer"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("BackChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("ClientClaimsPrefix") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("ConsentLifetime") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("integer"); - - b.Property("EnableLocalLogin") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("integer"); - - b.Property("IncludeJwtId") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp without time zone"); - - b.Property("LogoUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("PairWiseSubjectSalt") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ProtocolType") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("integer"); - - b.Property("RefreshTokenUsage") - .HasColumnType("integer"); - - b.Property("RequireClientSecret") - .HasColumnType("boolean"); - - b.Property("RequireConsent") - .HasColumnType("boolean"); - - b.Property("RequirePkce") - .HasColumnType("boolean"); - - b.Property("RequireRequestObject") - .HasColumnType("boolean"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp without time zone"); - - b.Property("UserCodeType") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("UserSsoLifetime") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("Clients"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Origin") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("character varying(150)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientCorsOrigins"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("GrantType") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientIdPRestrictions"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("PostLogoutRedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientPostLogoutRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientProperties"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("RedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("Expiration") - .HasColumnType("timestamp without time zone"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientSecrets"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp without time zone"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("IdentityResources"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") - .WithMany("UserClaims") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") - .WithMany("Properties") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.cs b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.cs deleted file mode 100644 index fec7b0f0..00000000 --- a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210216205007_Initial.cs +++ /dev/null @@ -1,658 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration -{ - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "ApiResources", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Enabled = table.Column(type: "boolean", nullable: false), - Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - AllowedAccessTokenSigningAlgorithms = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), - ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false), - Updated = table.Column(type: "timestamp without time zone", nullable: true), - LastAccessed = table.Column(type: "timestamp without time zone", nullable: true), - NonEditable = table.Column(type: "boolean", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiResources", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ApiScopes", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Enabled = table.Column(type: "boolean", nullable: false), - Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - Required = table.Column(type: "boolean", nullable: false), - Emphasize = table.Column(type: "boolean", nullable: false), - ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiScopes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Clients", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Enabled = table.Column(type: "boolean", nullable: false), - ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - ProtocolType = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - RequireClientSecret = table.Column(type: "boolean", nullable: false), - ClientName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - ClientUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), - LogoUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), - RequireConsent = table.Column(type: "boolean", nullable: false), - AllowRememberConsent = table.Column(type: "boolean", nullable: false), - AlwaysIncludeUserClaimsInIdToken = table.Column(type: "boolean", nullable: false), - RequirePkce = table.Column(type: "boolean", nullable: false), - AllowPlainTextPkce = table.Column(type: "boolean", nullable: false), - RequireRequestObject = table.Column(type: "boolean", nullable: false), - AllowAccessTokensViaBrowser = table.Column(type: "boolean", nullable: false), - FrontChannelLogoutUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), - FrontChannelLogoutSessionRequired = table.Column(type: "boolean", nullable: false), - BackChannelLogoutUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), - BackChannelLogoutSessionRequired = table.Column(type: "boolean", nullable: false), - AllowOfflineAccess = table.Column(type: "boolean", nullable: false), - IdentityTokenLifetime = table.Column(type: "integer", nullable: false), - AllowedIdentityTokenSigningAlgorithms = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), - AccessTokenLifetime = table.Column(type: "integer", nullable: false), - AuthorizationCodeLifetime = table.Column(type: "integer", nullable: false), - ConsentLifetime = table.Column(type: "integer", nullable: true), - AbsoluteRefreshTokenLifetime = table.Column(type: "integer", nullable: false), - SlidingRefreshTokenLifetime = table.Column(type: "integer", nullable: false), - RefreshTokenUsage = table.Column(type: "integer", nullable: false), - UpdateAccessTokenClaimsOnRefresh = table.Column(type: "boolean", nullable: false), - RefreshTokenExpiration = table.Column(type: "integer", nullable: false), - AccessTokenType = table.Column(type: "integer", nullable: false), - EnableLocalLogin = table.Column(type: "boolean", nullable: false), - IncludeJwtId = table.Column(type: "boolean", nullable: false), - AlwaysSendClientClaims = table.Column(type: "boolean", nullable: false), - ClientClaimsPrefix = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - PairWiseSubjectSalt = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - Created = table.Column(type: "timestamp without time zone", nullable: false), - Updated = table.Column(type: "timestamp without time zone", nullable: true), - LastAccessed = table.Column(type: "timestamp without time zone", nullable: true), - UserSsoLifetime = table.Column(type: "integer", nullable: true), - UserCodeType = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), - DeviceCodeLifetime = table.Column(type: "integer", nullable: false), - NonEditable = table.Column(type: "boolean", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Clients", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "IdentityResources", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Enabled = table.Column(type: "boolean", nullable: false), - Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - Required = table.Column(type: "boolean", nullable: false), - Emphasize = table.Column(type: "boolean", nullable: false), - ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false), - Updated = table.Column(type: "timestamp without time zone", nullable: true), - NonEditable = table.Column(type: "boolean", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityResources", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ApiResourceClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ApiResourceId = table.Column(type: "integer", nullable: false), - Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiResourceClaims", x => x.Id); - table.ForeignKey( - name: "FK_ApiResourceClaims_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiResourceProperties", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ApiResourceId = table.Column(type: "integer", nullable: false), - Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiResourceProperties", x => x.Id); - table.ForeignKey( - name: "FK_ApiResourceProperties_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiResourceScopes", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Scope = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - ApiResourceId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiResourceScopes", x => x.Id); - table.ForeignKey( - name: "FK_ApiResourceScopes_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiResourceSecrets", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ApiResourceId = table.Column(type: "integer", nullable: false), - Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - Value = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: false), - Expiration = table.Column(type: "timestamp without time zone", nullable: true), - Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiResourceSecrets", x => x.Id); - table.ForeignKey( - name: "FK_ApiResourceSecrets_ApiResources_ApiResourceId", - column: x => x.ApiResourceId, - principalTable: "ApiResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiScopeClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ScopeId = table.Column(type: "integer", nullable: false), - Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); - table.ForeignKey( - name: "FK_ApiScopeClaims_ApiScopes_ScopeId", - column: x => x.ScopeId, - principalTable: "ApiScopes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ApiScopeProperties", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ScopeId = table.Column(type: "integer", nullable: false), - Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiScopeProperties", x => x.Id); - table.ForeignKey( - name: "FK_ApiScopeProperties_ApiScopes_ScopeId", - column: x => x.ScopeId, - principalTable: "ApiScopes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), - ClientId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientClaims", x => x.Id); - table.ForeignKey( - name: "FK_ClientClaims_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientCorsOrigins", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Origin = table.Column(type: "character varying(150)", maxLength: 150, nullable: false), - ClientId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id); - table.ForeignKey( - name: "FK_ClientCorsOrigins_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientGrantTypes", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - GrantType = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), - ClientId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientGrantTypes", x => x.Id); - table.ForeignKey( - name: "FK_ClientGrantTypes_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientIdPRestrictions", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Provider = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - ClientId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id); - table.ForeignKey( - name: "FK_ClientIdPRestrictions_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientPostLogoutRedirectUris", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - PostLogoutRedirectUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), - ClientId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id); - table.ForeignKey( - name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientProperties", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ClientId = table.Column(type: "integer", nullable: false), - Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientProperties", x => x.Id); - table.ForeignKey( - name: "FK_ClientProperties_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientRedirectUris", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RedirectUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), - ClientId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientRedirectUris", x => x.Id); - table.ForeignKey( - name: "FK_ClientRedirectUris_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientScopes", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Scope = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - ClientId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientScopes", x => x.Id); - table.ForeignKey( - name: "FK_ClientScopes_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ClientSecrets", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ClientId = table.Column(type: "integer", nullable: false), - Description = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), - Value = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: false), - Expiration = table.Column(type: "timestamp without time zone", nullable: true), - Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), - Created = table.Column(type: "timestamp without time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ClientSecrets", x => x.Id); - table.ForeignKey( - name: "FK_ClientSecrets_Clients_ClientId", - column: x => x.ClientId, - principalTable: "Clients", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IdentityResourceClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - IdentityResourceId = table.Column(type: "integer", nullable: false), - Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityResourceClaims", x => x.Id); - table.ForeignKey( - name: "FK_IdentityResourceClaims_IdentityResources_IdentityResourceId", - column: x => x.IdentityResourceId, - principalTable: "IdentityResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "IdentityResourceProperties", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - IdentityResourceId = table.Column(type: "integer", nullable: false), - Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), - Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityResourceProperties", x => x.Id); - table.ForeignKey( - name: "FK_IdentityResourceProperties_IdentityResources_IdentityResour~", - column: x => x.IdentityResourceId, - principalTable: "IdentityResources", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_ApiResourceClaims_ApiResourceId", - table: "ApiResourceClaims", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiResourceProperties_ApiResourceId", - table: "ApiResourceProperties", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiResources_Name", - table: "ApiResources", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ApiResourceScopes_ApiResourceId", - table: "ApiResourceScopes", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiResourceSecrets_ApiResourceId", - table: "ApiResourceSecrets", - column: "ApiResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiScopeClaims_ScopeId", - table: "ApiScopeClaims", - column: "ScopeId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiScopeProperties_ScopeId", - table: "ApiScopeProperties", - column: "ScopeId"); - - migrationBuilder.CreateIndex( - name: "IX_ApiScopes_Name", - table: "ApiScopes", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ClientClaims_ClientId", - table: "ClientClaims", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientCorsOrigins_ClientId", - table: "ClientCorsOrigins", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientGrantTypes_ClientId", - table: "ClientGrantTypes", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientIdPRestrictions_ClientId", - table: "ClientIdPRestrictions", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientPostLogoutRedirectUris_ClientId", - table: "ClientPostLogoutRedirectUris", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientProperties_ClientId", - table: "ClientProperties", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientRedirectUris_ClientId", - table: "ClientRedirectUris", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_Clients_ClientId", - table: "Clients", - column: "ClientId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ClientScopes_ClientId", - table: "ClientScopes", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_ClientSecrets_ClientId", - table: "ClientSecrets", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_IdentityResourceClaims_IdentityResourceId", - table: "IdentityResourceClaims", - column: "IdentityResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_IdentityResourceProperties_IdentityResourceId", - table: "IdentityResourceProperties", - column: "IdentityResourceId"); - - migrationBuilder.CreateIndex( - name: "IX_IdentityResources_Name", - table: "IdentityResources", - column: "Name", - unique: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ApiResourceClaims"); - - migrationBuilder.DropTable( - name: "ApiResourceProperties"); - - migrationBuilder.DropTable( - name: "ApiResourceScopes"); - - migrationBuilder.DropTable( - name: "ApiResourceSecrets"); - - migrationBuilder.DropTable( - name: "ApiScopeClaims"); - - migrationBuilder.DropTable( - name: "ApiScopeProperties"); - - migrationBuilder.DropTable( - name: "ClientClaims"); - - migrationBuilder.DropTable( - name: "ClientCorsOrigins"); - - migrationBuilder.DropTable( - name: "ClientGrantTypes"); - - migrationBuilder.DropTable( - name: "ClientIdPRestrictions"); - - migrationBuilder.DropTable( - name: "ClientPostLogoutRedirectUris"); - - migrationBuilder.DropTable( - name: "ClientProperties"); - - migrationBuilder.DropTable( - name: "ClientRedirectUris"); - - migrationBuilder.DropTable( - name: "ClientScopes"); - - migrationBuilder.DropTable( - name: "ClientSecrets"); - - migrationBuilder.DropTable( - name: "IdentityResourceClaims"); - - migrationBuilder.DropTable( - name: "IdentityResourceProperties"); - - migrationBuilder.DropTable( - name: "ApiResources"); - - migrationBuilder.DropTable( - name: "ApiScopes"); - - migrationBuilder.DropTable( - name: "Clients"); - - migrationBuilder.DropTable( - name: "IdentityResources"); - } - } -} diff --git a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs deleted file mode 100644 index c11ac33a..00000000 --- a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs +++ /dev/null @@ -1,983 +0,0 @@ -// -using System; -using IdentityServer4.EntityFramework.DbContexts; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration -{ - [DbContext(typeof(ConfigurationDbContext))] - partial class ConfigurationDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.3") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AllowedAccessTokenSigningAlgorithms") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp without time zone"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp without time zone"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiResources"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceScopes"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("Expiration") - .HasColumnType("timestamp without time zone"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceSecrets"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiScopes"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.ToTable("ApiScopeClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.ToTable("ApiScopeProperties"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenType") - .HasColumnType("integer"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("boolean"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("boolean"); - - b.Property("AllowRememberConsent") - .HasColumnType("boolean"); - - b.Property("AllowedIdentityTokenSigningAlgorithms") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("boolean"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("boolean"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("integer"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("BackChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("ClientClaimsPrefix") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("ConsentLifetime") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("integer"); - - b.Property("EnableLocalLogin") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("integer"); - - b.Property("IncludeJwtId") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp without time zone"); - - b.Property("LogoUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("PairWiseSubjectSalt") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ProtocolType") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("integer"); - - b.Property("RefreshTokenUsage") - .HasColumnType("integer"); - - b.Property("RequireClientSecret") - .HasColumnType("boolean"); - - b.Property("RequireConsent") - .HasColumnType("boolean"); - - b.Property("RequirePkce") - .HasColumnType("boolean"); - - b.Property("RequireRequestObject") - .HasColumnType("boolean"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp without time zone"); - - b.Property("UserCodeType") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("UserSsoLifetime") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("Clients"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Origin") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("character varying(150)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientCorsOrigins"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("GrantType") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientIdPRestrictions"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("PostLogoutRedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientPostLogoutRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientProperties"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("RedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("Expiration") - .HasColumnType("timestamp without time zone"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientSecrets"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Created") - .HasColumnType("timestamp without time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp without time zone"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("IdentityResources"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") - .WithMany("UserClaims") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") - .WithMany("Properties") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.Designer.cs deleted file mode 100644 index bfdf578f..00000000 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.Designer.cs +++ /dev/null @@ -1,778 +0,0 @@ -// -using System; -using Kyoo; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Models.DatabaseMigrations.Internal -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20210228232014_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) - .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) - .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.3") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("CollectionLibrary", b => - { - b.Property("CollectionsID") - .HasColumnType("integer"); - - b.Property("LibrariesID") - .HasColumnType("integer"); - - b.HasKey("CollectionsID", "LibrariesID"); - - b.HasIndex("LibrariesID"); - - b.ToTable("CollectionLibrary"); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.Property("CollectionsID") - .HasColumnType("integer"); - - b.Property("ShowsID") - .HasColumnType("integer"); - - b.HasKey("CollectionsID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("CollectionShow"); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.Property("GenresID") - .HasColumnType("integer"); - - b.Property("ShowsID") - .HasColumnType("integer"); - - b.HasKey("GenresID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("GenreShow"); - }); - - modelBuilder.Entity("Kyoo.Models.Collection", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Collections"); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AbsoluteNumber") - .HasColumnType("integer"); - - b.Property("EpisodeNumber") - .HasColumnType("integer"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("ReleaseDate") - .HasColumnType("timestamp without time zone"); - - b.Property("Runtime") - .HasColumnType("integer"); - - b.Property("SeasonID") - .HasColumnType("integer"); - - b.Property("SeasonNumber") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("SeasonID"); - - b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") - .IsUnique(); - - b.ToTable("Episodes"); - }); - - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Genres"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Paths") - .HasColumnType("text[]"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Libraries"); - }); - - modelBuilder.Entity("Kyoo.Models.LibraryLink", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("CollectionID") - .HasColumnType("integer"); - - b.Property("LibraryID") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("CollectionID"); - - b.HasIndex("ShowID"); - - b.HasIndex("LibraryID", "CollectionID") - .IsUnique(); - - b.HasIndex("LibraryID", "ShowID") - .IsUnique(); - - b.ToTable("LibraryLink"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("DataID") - .HasColumnType("text"); - - b.Property("EpisodeID") - .HasColumnType("integer"); - - b.Property("Link") - .HasColumnType("text"); - - b.Property("PeopleID") - .HasColumnType("integer"); - - b.Property("ProviderID") - .HasColumnType("integer"); - - b.Property("SeasonID") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("EpisodeID"); - - b.HasIndex("PeopleID"); - - b.HasIndex("ProviderID"); - - b.HasIndex("SeasonID"); - - b.HasIndex("ShowID"); - - b.ToTable("MetadataIds"); - }); - - modelBuilder.Entity("Kyoo.Models.People", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("People"); - }); - - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("PeopleID") - .HasColumnType("integer"); - - b.Property("Role") - .HasColumnType("text"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("PeopleID"); - - b.HasIndex("ShowID"); - - b.ToTable("PeopleRoles"); - }); - - modelBuilder.Entity("Kyoo.Models.ProviderID", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Logo") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Providers"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("SeasonNumber") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("ShowID", "SeasonNumber") - .IsUnique(); - - b.ToTable("Seasons"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Aliases") - .HasColumnType("text[]"); - - b.Property("Backdrop") - .HasColumnType("text"); - - b.Property("EndYear") - .HasColumnType("integer"); - - b.Property("IsMovie") - .HasColumnType("boolean"); - - b.Property("Logo") - .HasColumnType("text"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("StartYear") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("StudioID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("TrailerUrl") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.HasIndex("StudioID"); - - b.ToTable("Shows"); - }); - - modelBuilder.Entity("Kyoo.Models.Studio", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Studios"); - }); - - modelBuilder.Entity("Kyoo.Models.Track", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Codec") - .HasColumnType("text"); - - b.Property("EpisodeID") - .HasColumnType("integer"); - - b.Property("IsDefault") - .HasColumnType("boolean"); - - b.Property("IsExternal") - .HasColumnType("boolean"); - - b.Property("IsForced") - .HasColumnType("boolean"); - - b.Property("Language") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("EpisodeID"); - - b.ToTable("Tracks"); - }); - - modelBuilder.Entity("LibraryProviderID", b => - { - b.Property("LibrariesID") - .HasColumnType("integer"); - - b.Property("ProvidersID") - .HasColumnType("integer"); - - b.HasKey("LibrariesID", "ProvidersID"); - - b.HasIndex("ProvidersID"); - - b.ToTable("LibraryProviderID"); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.Property("LibrariesID") - .HasColumnType("integer"); - - b.Property("ShowsID") - .HasColumnType("integer"); - - b.HasKey("LibrariesID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("LibraryShow"); - }); - - modelBuilder.Entity("CollectionLibrary", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.HasOne("Kyoo.Models.Genre", null) - .WithMany() - .HasForeignKey("GenresID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.HasOne("Kyoo.Models.Season", "Season") - .WithMany("Episodes") - .HasForeignKey("SeasonID"); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("Episodes") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.LibraryLink", b => - { - b.HasOne("Kyoo.Models.Collection", "Collection") - .WithMany() - .HasForeignKey("CollectionID"); - - b.HasOne("Kyoo.Models.Library", "Library") - .WithMany() - .HasForeignKey("LibraryID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany() - .HasForeignKey("ShowID"); - - b.Navigation("Collection"); - - b.Navigation("Library"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "Episode") - .WithMany("ExternalIDs") - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.People", "People") - .WithMany("ExternalIDs") - .HasForeignKey("PeopleID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.ProviderID", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Season", "Season") - .WithMany("ExternalIDs") - .HasForeignKey("SeasonID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("ExternalIDs") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Episode"); - - b.Navigation("People"); - - b.Navigation("Provider"); - - b.Navigation("Season"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => - { - b.HasOne("Kyoo.Models.People", "People") - .WithMany("Roles") - .HasForeignKey("PeopleID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("People") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("People"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("Seasons") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.HasOne("Kyoo.Models.Studio", "Studio") - .WithMany("Shows") - .HasForeignKey("StudioID"); - - b.Navigation("Studio"); - }); - - modelBuilder.Entity("Kyoo.Models.Track", b => - { - b.HasOne("Kyoo.Models.Episode", "Episode") - .WithMany("Tracks") - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Episode"); - }); - - modelBuilder.Entity("LibraryProviderID", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.ProviderID", null) - .WithMany() - .HasForeignKey("ProvidersID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Tracks"); - }); - - modelBuilder.Entity("Kyoo.Models.People", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Roles"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - - b.Navigation("People"); - - b.Navigation("Seasons"); - }); - - modelBuilder.Entity("Kyoo.Models.Studio", b => - { - b.Navigation("Shows"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.cs deleted file mode 100644 index d211a75c..00000000 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210228232014_Initial.cs +++ /dev/null @@ -1,662 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Models.DatabaseMigrations.Internal -{ - 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", "finished,airing,planned,unknown") - .Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,font"); - - 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), - Poster = table.Column(type: "text", 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), - Poster = table.Column(type: "text", 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), - Logo = table.Column(type: "text", 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: "CollectionLibrary", - columns: table => new - { - CollectionsID = table.Column(type: "integer", nullable: false), - LibrariesID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CollectionLibrary", x => new { x.CollectionsID, x.LibrariesID }); - table.ForeignKey( - name: "FK_CollectionLibrary_Collections_CollectionsID", - column: x => x.CollectionsID, - principalTable: "Collections", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_CollectionLibrary_Libraries_LibrariesID", - column: x => x.LibrariesID, - principalTable: "Libraries", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "LibraryProviderID", - columns: table => new - { - LibrariesID = table.Column(type: "integer", nullable: false), - ProvidersID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_LibraryProviderID", x => new { x.LibrariesID, x.ProvidersID }); - table.ForeignKey( - name: "FK_LibraryProviderID_Libraries_LibrariesID", - column: x => x.LibrariesID, - principalTable: "Libraries", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_LibraryProviderID_Providers_ProvidersID", - column: x => x.ProvidersID, - 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: "integer", nullable: true), - TrailerUrl = table.Column(type: "text", nullable: true), - StartYear = table.Column(type: "integer", nullable: true), - EndYear = table.Column(type: "integer", nullable: true), - Poster = table.Column(type: "text", nullable: true), - Logo = table.Column(type: "text", nullable: true), - Backdrop = table.Column(type: "text", nullable: true), - IsMovie = table.Column(type: "boolean", nullable: false), - StudioID = table.Column(type: "integer", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Shows", x => x.ID); - table.ForeignKey( - name: "FK_Shows_Studios_StudioID", - column: x => x.StudioID, - principalTable: "Studios", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "CollectionShow", - columns: table => new - { - CollectionsID = table.Column(type: "integer", nullable: false), - ShowsID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CollectionShow", x => new { x.CollectionsID, x.ShowsID }); - table.ForeignKey( - name: "FK_CollectionShow_Collections_CollectionsID", - column: x => x.CollectionsID, - principalTable: "Collections", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_CollectionShow_Shows_ShowsID", - column: x => x.ShowsID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "GenreShow", - columns: table => new - { - GenresID = table.Column(type: "integer", nullable: false), - ShowsID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_GenreShow", x => new { x.GenresID, x.ShowsID }); - table.ForeignKey( - name: "FK_GenreShow_Genres_GenresID", - column: x => x.GenresID, - principalTable: "Genres", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_GenreShow_Shows_ShowsID", - column: x => x.ShowsID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "LibraryLink", - columns: table => new - { - ID = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - LibraryID = table.Column(type: "integer", nullable: false), - ShowID = table.Column(type: "integer", nullable: true), - CollectionID = table.Column(type: "integer", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_LibraryLink", x => x.ID); - table.ForeignKey( - name: "FK_LibraryLink_Collections_CollectionID", - column: x => x.CollectionID, - principalTable: "Collections", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_LibraryLink_Libraries_LibraryID", - column: x => x.LibraryID, - principalTable: "Libraries", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_LibraryLink_Shows_ShowID", - column: x => x.ShowID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "LibraryShow", - columns: table => new - { - LibrariesID = table.Column(type: "integer", nullable: false), - ShowsID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_LibraryShow", x => new { x.LibrariesID, x.ShowsID }); - table.ForeignKey( - name: "FK_LibraryShow_Libraries_LibrariesID", - column: x => x.LibrariesID, - principalTable: "Libraries", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_LibraryShow_Shows_ShowsID", - column: x => x.ShowsID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "PeopleRoles", - columns: table => new - { - ID = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - PeopleID = table.Column(type: "integer", nullable: false), - ShowID = table.Column(type: "integer", nullable: false), - Role = table.Column(type: "text", nullable: true), - Type = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_PeopleRoles", x => x.ID); - table.ForeignKey( - name: "FK_PeopleRoles_People_PeopleID", - column: x => x.PeopleID, - principalTable: "People", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_PeopleRoles_Shows_ShowID", - column: x => x.ShowID, - 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), - ShowID = table.Column(type: "integer", nullable: false), - SeasonNumber = table.Column(type: "integer", nullable: false), - Title = table.Column(type: "text", nullable: true), - Overview = table.Column(type: "text", nullable: true), - Year = table.Column(type: "integer", nullable: true), - Poster = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Seasons", x => x.ID); - table.ForeignKey( - name: "FK_Seasons_Shows_ShowID", - column: x => x.ShowID, - 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), - ShowID = table.Column(type: "integer", nullable: false), - SeasonID = table.Column(type: "integer", nullable: true), - SeasonNumber = table.Column(type: "integer", nullable: false), - EpisodeNumber = table.Column(type: "integer", nullable: false), - AbsoluteNumber = table.Column(type: "integer", nullable: false), - Path = table.Column(type: "text", nullable: true), - Title = table.Column(type: "text", nullable: true), - Overview = table.Column(type: "text", nullable: true), - ReleaseDate = table.Column(type: "timestamp without time zone", nullable: true), - Runtime = table.Column(type: "integer", nullable: false), - Poster = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Episodes", x => x.ID); - table.ForeignKey( - name: "FK_Episodes_Seasons_SeasonID", - column: x => x.SeasonID, - principalTable: "Seasons", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Episodes_Shows_ShowID", - column: x => x.ShowID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "MetadataIds", - columns: table => new - { - ID = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ProviderID = table.Column(type: "integer", nullable: false), - ShowID = table.Column(type: "integer", nullable: true), - EpisodeID = table.Column(type: "integer", nullable: true), - SeasonID = table.Column(type: "integer", nullable: true), - PeopleID = table.Column(type: "integer", nullable: true), - DataID = table.Column(type: "text", nullable: true), - Link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MetadataIds", x => x.ID); - table.ForeignKey( - name: "FK_MetadataIds_Episodes_EpisodeID", - column: x => x.EpisodeID, - principalTable: "Episodes", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MetadataIds_People_PeopleID", - column: x => x.PeopleID, - principalTable: "People", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MetadataIds_Providers_ProviderID", - column: x => x.ProviderID, - principalTable: "Providers", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MetadataIds_Seasons_SeasonID", - column: x => x.SeasonID, - principalTable: "Seasons", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MetadataIds_Shows_ShowID", - column: x => x.ShowID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Tracks", - columns: table => new - { - ID = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - EpisodeID = table.Column(type: "integer", nullable: false), - IsDefault = table.Column(type: "boolean", nullable: false), - IsForced = table.Column(type: "boolean", nullable: false), - IsExternal = table.Column(type: "boolean", nullable: false), - Title = table.Column(type: "text", nullable: true), - Language = table.Column(type: "text", nullable: true), - Codec = table.Column(type: "text", nullable: true), - Path = table.Column(type: "text", nullable: true), - Type = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Tracks", x => x.ID); - table.ForeignKey( - name: "FK_Tracks_Episodes_EpisodeID", - column: x => x.EpisodeID, - principalTable: "Episodes", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_CollectionLibrary_LibrariesID", - table: "CollectionLibrary", - column: "LibrariesID"); - - migrationBuilder.CreateIndex( - name: "IX_Collections_Slug", - table: "Collections", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_CollectionShow_ShowsID", - table: "CollectionShow", - column: "ShowsID"); - - migrationBuilder.CreateIndex( - name: "IX_Episodes_SeasonID", - table: "Episodes", - column: "SeasonID"); - - migrationBuilder.CreateIndex( - name: "IX_Episodes_ShowID_SeasonNumber_EpisodeNumber_AbsoluteNumber", - table: "Episodes", - columns: new[] { "ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Genres_Slug", - table: "Genres", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_GenreShow_ShowsID", - table: "GenreShow", - column: "ShowsID"); - - migrationBuilder.CreateIndex( - name: "IX_Libraries_Slug", - table: "Libraries", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_LibraryLink_CollectionID", - table: "LibraryLink", - column: "CollectionID"); - - migrationBuilder.CreateIndex( - name: "IX_LibraryLink_LibraryID_CollectionID", - table: "LibraryLink", - columns: new[] { "LibraryID", "CollectionID" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_LibraryLink_LibraryID_ShowID", - table: "LibraryLink", - columns: new[] { "LibraryID", "ShowID" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_LibraryLink_ShowID", - table: "LibraryLink", - column: "ShowID"); - - migrationBuilder.CreateIndex( - name: "IX_LibraryProviderID_ProvidersID", - table: "LibraryProviderID", - column: "ProvidersID"); - - migrationBuilder.CreateIndex( - name: "IX_LibraryShow_ShowsID", - table: "LibraryShow", - column: "ShowsID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_EpisodeID", - table: "MetadataIds", - column: "EpisodeID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_PeopleID", - table: "MetadataIds", - column: "PeopleID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_ProviderID", - table: "MetadataIds", - column: "ProviderID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_SeasonID", - table: "MetadataIds", - column: "SeasonID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_ShowID", - table: "MetadataIds", - column: "ShowID"); - - migrationBuilder.CreateIndex( - name: "IX_People_Slug", - table: "People", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_PeopleRoles_PeopleID", - table: "PeopleRoles", - column: "PeopleID"); - - migrationBuilder.CreateIndex( - name: "IX_PeopleRoles_ShowID", - table: "PeopleRoles", - column: "ShowID"); - - migrationBuilder.CreateIndex( - name: "IX_Providers_Slug", - table: "Providers", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Seasons_ShowID_SeasonNumber", - table: "Seasons", - columns: new[] { "ShowID", "SeasonNumber" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Shows_Slug", - table: "Shows", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Shows_StudioID", - table: "Shows", - column: "StudioID"); - - migrationBuilder.CreateIndex( - name: "IX_Studios_Slug", - table: "Studios", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Tracks_EpisodeID", - table: "Tracks", - column: "EpisodeID"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "CollectionLibrary"); - - migrationBuilder.DropTable( - name: "CollectionShow"); - - migrationBuilder.DropTable( - name: "GenreShow"); - - migrationBuilder.DropTable( - name: "LibraryLink"); - - migrationBuilder.DropTable( - name: "LibraryProviderID"); - - migrationBuilder.DropTable( - name: "LibraryShow"); - - migrationBuilder.DropTable( - name: "MetadataIds"); - - migrationBuilder.DropTable( - name: "PeopleRoles"); - - migrationBuilder.DropTable( - name: "Tracks"); - - migrationBuilder.DropTable( - name: "Genres"); - - migrationBuilder.DropTable( - name: "Collections"); - - migrationBuilder.DropTable( - name: "Libraries"); - - migrationBuilder.DropTable( - name: "Providers"); - - migrationBuilder.DropTable( - name: "People"); - - migrationBuilder.DropTable( - name: "Episodes"); - - migrationBuilder.DropTable( - name: "Seasons"); - - migrationBuilder.DropTable( - name: "Shows"); - - migrationBuilder.DropTable( - name: "Studios"); - } - } -} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs deleted file mode 100644 index 2d9d6b75..00000000 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ /dev/null @@ -1,776 +0,0 @@ -// -using System; -using Kyoo; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Models.DatabaseMigrations.Internal -{ - [DbContext(typeof(DatabaseContext))] - partial class DatabaseContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) - .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) - .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.3") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("CollectionLibrary", b => - { - b.Property("CollectionsID") - .HasColumnType("integer"); - - b.Property("LibrariesID") - .HasColumnType("integer"); - - b.HasKey("CollectionsID", "LibrariesID"); - - b.HasIndex("LibrariesID"); - - b.ToTable("CollectionLibrary"); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.Property("CollectionsID") - .HasColumnType("integer"); - - b.Property("ShowsID") - .HasColumnType("integer"); - - b.HasKey("CollectionsID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("CollectionShow"); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.Property("GenresID") - .HasColumnType("integer"); - - b.Property("ShowsID") - .HasColumnType("integer"); - - b.HasKey("GenresID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("GenreShow"); - }); - - modelBuilder.Entity("Kyoo.Models.Collection", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Collections"); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AbsoluteNumber") - .HasColumnType("integer"); - - b.Property("EpisodeNumber") - .HasColumnType("integer"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("ReleaseDate") - .HasColumnType("timestamp without time zone"); - - b.Property("Runtime") - .HasColumnType("integer"); - - b.Property("SeasonID") - .HasColumnType("integer"); - - b.Property("SeasonNumber") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("SeasonID"); - - b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") - .IsUnique(); - - b.ToTable("Episodes"); - }); - - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Genres"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Paths") - .HasColumnType("text[]"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Libraries"); - }); - - modelBuilder.Entity("Kyoo.Models.LibraryLink", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("CollectionID") - .HasColumnType("integer"); - - b.Property("LibraryID") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("CollectionID"); - - b.HasIndex("ShowID"); - - b.HasIndex("LibraryID", "CollectionID") - .IsUnique(); - - b.HasIndex("LibraryID", "ShowID") - .IsUnique(); - - b.ToTable("LibraryLink"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("DataID") - .HasColumnType("text"); - - b.Property("EpisodeID") - .HasColumnType("integer"); - - b.Property("Link") - .HasColumnType("text"); - - b.Property("PeopleID") - .HasColumnType("integer"); - - b.Property("ProviderID") - .HasColumnType("integer"); - - b.Property("SeasonID") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("EpisodeID"); - - b.HasIndex("PeopleID"); - - b.HasIndex("ProviderID"); - - b.HasIndex("SeasonID"); - - b.HasIndex("ShowID"); - - b.ToTable("MetadataIds"); - }); - - modelBuilder.Entity("Kyoo.Models.People", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("People"); - }); - - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("PeopleID") - .HasColumnType("integer"); - - b.Property("Role") - .HasColumnType("text"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("PeopleID"); - - b.HasIndex("ShowID"); - - b.ToTable("PeopleRoles"); - }); - - modelBuilder.Entity("Kyoo.Models.ProviderID", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Logo") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Providers"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("SeasonNumber") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("ShowID", "SeasonNumber") - .IsUnique(); - - b.ToTable("Seasons"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Aliases") - .HasColumnType("text[]"); - - b.Property("Backdrop") - .HasColumnType("text"); - - b.Property("EndYear") - .HasColumnType("integer"); - - b.Property("IsMovie") - .HasColumnType("boolean"); - - b.Property("Logo") - .HasColumnType("text"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("StartYear") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("StudioID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("TrailerUrl") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.HasIndex("StudioID"); - - b.ToTable("Shows"); - }); - - modelBuilder.Entity("Kyoo.Models.Studio", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Studios"); - }); - - modelBuilder.Entity("Kyoo.Models.Track", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Codec") - .HasColumnType("text"); - - b.Property("EpisodeID") - .HasColumnType("integer"); - - b.Property("IsDefault") - .HasColumnType("boolean"); - - b.Property("IsExternal") - .HasColumnType("boolean"); - - b.Property("IsForced") - .HasColumnType("boolean"); - - b.Property("Language") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("EpisodeID"); - - b.ToTable("Tracks"); - }); - - modelBuilder.Entity("LibraryProviderID", b => - { - b.Property("LibrariesID") - .HasColumnType("integer"); - - b.Property("ProvidersID") - .HasColumnType("integer"); - - b.HasKey("LibrariesID", "ProvidersID"); - - b.HasIndex("ProvidersID"); - - b.ToTable("LibraryProviderID"); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.Property("LibrariesID") - .HasColumnType("integer"); - - b.Property("ShowsID") - .HasColumnType("integer"); - - b.HasKey("LibrariesID", "ShowsID"); - - b.HasIndex("ShowsID"); - - b.ToTable("LibraryShow"); - }); - - modelBuilder.Entity("CollectionLibrary", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("CollectionShow", b => - { - b.HasOne("Kyoo.Models.Collection", null) - .WithMany() - .HasForeignKey("CollectionsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("GenreShow", b => - { - b.HasOne("Kyoo.Models.Genre", null) - .WithMany() - .HasForeignKey("GenresID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.HasOne("Kyoo.Models.Season", "Season") - .WithMany("Episodes") - .HasForeignKey("SeasonID"); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("Episodes") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.LibraryLink", b => - { - b.HasOne("Kyoo.Models.Collection", "Collection") - .WithMany() - .HasForeignKey("CollectionID"); - - b.HasOne("Kyoo.Models.Library", "Library") - .WithMany() - .HasForeignKey("LibraryID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany() - .HasForeignKey("ShowID"); - - b.Navigation("Collection"); - - b.Navigation("Library"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "Episode") - .WithMany("ExternalIDs") - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.People", "People") - .WithMany("ExternalIDs") - .HasForeignKey("PeopleID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.ProviderID", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Season", "Season") - .WithMany("ExternalIDs") - .HasForeignKey("SeasonID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("ExternalIDs") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Episode"); - - b.Navigation("People"); - - b.Navigation("Provider"); - - b.Navigation("Season"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => - { - b.HasOne("Kyoo.Models.People", "People") - .WithMany("Roles") - .HasForeignKey("PeopleID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("People") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("People"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("Seasons") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.HasOne("Kyoo.Models.Studio", "Studio") - .WithMany("Shows") - .HasForeignKey("StudioID"); - - b.Navigation("Studio"); - }); - - modelBuilder.Entity("Kyoo.Models.Track", b => - { - b.HasOne("Kyoo.Models.Episode", "Episode") - .WithMany("Tracks") - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Episode"); - }); - - modelBuilder.Entity("LibraryProviderID", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.ProviderID", null) - .WithMany() - .HasForeignKey("ProvidersID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("LibraryShow", b => - { - b.HasOne("Kyoo.Models.Library", null) - .WithMany() - .HasForeignKey("LibrariesID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", null) - .WithMany() - .HasForeignKey("ShowsID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Tracks"); - }); - - modelBuilder.Entity("Kyoo.Models.People", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Roles"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - - b.Navigation("People"); - - b.Navigation("Seasons"); - }); - - modelBuilder.Entity("Kyoo.Models.Studio", b => - { - b.Navigation("Shows"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 92ba2342..ff7f9868 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -122,6 +122,7 @@ namespace Kyoo.Controllers .GroupBy(Path.GetDirectoryName) .ToList(); + // TODO If the library's path end with a /, the regex is broken. IEnumerable tasks = shows.Select(x => x.First()); foreach (string[] showTasks in tasks.BatchBy(_parallelTasks)) await Task.WhenAll(showTasks diff --git a/Kyoo/appsettings.json b/Kyoo/appsettings.json index 7845b158..19f3d82f 100644 --- a/Kyoo/appsettings.json +++ b/Kyoo/appsettings.json @@ -18,7 +18,7 @@ "ConnectionStrings": { "Database": "Server=127.0.0.1; Port=5432; Database=kyooDB; User Id=kyoo; Password=kyooPassword; Pooling=true; MaxPoolSize=95; Timeout=30;" }, - "parallelTasks": "40", + "parallelTasks": "1", "scheduledTasks": { "scan": "24:00:00" diff --git a/transcoder b/transcoder index 2d15a6ce..3885dca7 160000 --- a/transcoder +++ b/transcoder @@ -1 +1 @@ -Subproject commit 2d15a6cea98639e286083c96443f56a354ed2002 +Subproject commit 3885dca743bbde5d83cb3816646455856fc5c316 From b907e45a0845bc9763ff60fa031dc7bde9686f0b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 6 Mar 2021 20:21:35 +0100 Subject: [PATCH 23/54] Finishing to fix repositories --- .../Implementations/LibraryManager.cs | 3 + Kyoo.Common/Models/Link.cs | 56 +- Kyoo.Common/Models/Resources/Show.cs | 2 +- Kyoo.Common/Utility.cs | 15 +- Kyoo.CommonAPI/LocalRepository.cs | 1 + .../Repositories/CollectionRepository.cs | 1 + .../Repositories/EpisodeRepository.cs | 11 +- .../Repositories/GenreRepository.cs | 1 + .../Repositories/LibraryItemRepository.cs | 1 + .../Repositories/LibraryRepository.cs | 11 +- .../Repositories/PeopleRepository.cs | 6 +- .../Repositories/SeasonRepository.cs | 6 +- .../Repositories/ShowRepository.cs | 13 +- Kyoo/Models/DatabaseContext.cs | 10 +- .../20210306161631_Initial.Designer.cs | 985 ++++++++++++++++++ .../20210306161631_Initial.cs | 658 ++++++++++++ .../ConfigurationDbContextModelSnapshot.cs | 983 +++++++++++++++++ .../20210306181259_Initial.Designer.cs | 776 ++++++++++++++ .../Internal/20210306181259_Initial.cs | 604 +++++++++++ .../Internal/DatabaseContextModelSnapshot.cs | 774 ++++++++++++++ Kyoo/Tasks/Crawler.cs | 4 + 21 files changed, 4870 insertions(+), 51 deletions(-) create mode 100644 Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210306161631_Initial.Designer.cs create mode 100644 Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210306161631_Initial.cs create mode 100644 Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs create mode 100644 Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs create mode 100644 Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs create mode 100644 Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index fee5a457..c14f1251 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -263,6 +263,9 @@ namespace Kyoo.Controllers public Task Load(IResource obj, string member) { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + return (obj, member) switch { (Library l, nameof(Library.Providers)) => ProviderRepository diff --git a/Kyoo.Common/Models/Link.cs b/Kyoo.Common/Models/Link.cs index 8dee75bb..b78d3508 100644 --- a/Kyoo.Common/Models/Link.cs +++ b/Kyoo.Common/Models/Link.cs @@ -3,21 +3,63 @@ using System.Linq.Expressions; namespace Kyoo.Models { - public class Link - where T1 : class, IResource - where T2 : class, IResource + public class Link { - public static Expression, object>> PrimaryKey + public int FirstID { get; set; } + public int SecondID { get; set; } + + public Link() {} + + public Link(IResource first, IResource second) + { + FirstID = first.ID; + SecondID = second.ID; + } + + public static Link Create(IResource first, IResource second) + { + return new(first, second); + } + + public static Link Create(T first, T2 second) + where T : class, IResource + where T2 : class, IResource + { + return new(first, second); + } + + public static Expression> PrimaryKey { get { return x => new {LibraryID = x.FirstID, ProviderID = x.SecondID}; } } - - public int FirstID { get; set; } + } + + public class Link : Link + where T1 : class, IResource + where T2 : class, IResource + { public virtual T1 First { get; set; } - public int SecondID { get; set; } public virtual T2 Second { get; set; } + + + public Link() {} + + public Link(T1 first, T2 second) + : base(first, second) + { + First = first; + Second = second; + } + + public new static Expression, object>> PrimaryKey + { + get + { + return x => new {LibraryID = x.FirstID, ProviderID = x.SecondID}; + } + } } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 5eb50eed..520cfeb7 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -27,7 +27,7 @@ namespace Kyoo.Models [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - public int? StudioID { get; set; } + [SerializeIgnore] public int? StudioID { get; set; } [LoadableRelation] [EditableRelation] public virtual Studio Studio { get; set; } [LoadableRelation] [EditableRelation] public virtual ICollection Genres { get; set; } [LoadableRelation] [EditableRelation] public virtual ICollection People { get; set; } diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 688e4d54..f50d7018 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -282,6 +282,14 @@ namespace Kyoo while (enumerator.MoveNext()); } + public static void ForEach([CanBeNull] this IEnumerable self, Action action) + { + if (self == null) + return; + foreach (T i in self) + action(i); + } + private static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args) { MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public | BindingFlags.NonPublic) @@ -494,13 +502,6 @@ namespace Kyoo return RunGenericMethod(typeof(Enumerable), "SequenceEqual", type, first, second); } - public static void Test() - { - #if INTERNAL_LINKS - Console.WriteLine("Lib test"); -#endif - } - public static bool ResourceEquals([CanBeNull] T first, [CanBeNull] T second) where T : IResource { diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 0be49075..0e47c5b3 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -31,6 +31,7 @@ namespace Kyoo.Controllers public virtual void Dispose() { Database.Dispose(); + GC.SuppressFinalize(this); } public virtual ValueTask DisposeAsync() diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index d390e41f..44623818 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -25,6 +25,7 @@ namespace Kyoo.Controllers return; _disposed = true; _database.Dispose(); + GC.SuppressFinalize(this); } public override async ValueTask DisposeAsync() diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index e618c6b9..816b2232 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -32,6 +32,7 @@ namespace Kyoo.Controllers _disposed = true; _database.Dispose(); _providers.Dispose(); + GC.SuppressFinalize(this); } public override async ValueTask DisposeAsync() @@ -98,14 +99,8 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - if (obj.ExternalIDs != null) - foreach (MetadataID entry in obj.ExternalIDs) - _database.Entry(entry).State = EntityState.Added; - - if (obj.Tracks != null) - foreach (Track entry in obj.Tracks) - _database.Entry(entry).State = EntityState.Added; - + obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); + obj.Tracks.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index c9ef602d..d6e8363b 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -26,6 +26,7 @@ namespace Kyoo.Controllers return; _disposed = true; _database.Dispose(); + GC.SuppressFinalize(this); } public override async ValueTask DisposeAsync() diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index b02b3bea..8d32d19b 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -42,6 +42,7 @@ namespace Kyoo.Controllers _shows.Value.Dispose(); if (_collections.IsValueCreated) _collections.Value.Dispose(); + GC.SuppressFinalize(this); } public override async ValueTask DisposeAsync() diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index f91231ad..96a09ac7 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -4,10 +4,7 @@ using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; -using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { @@ -33,6 +30,7 @@ namespace Kyoo.Controllers _disposed = true; _database.Dispose(); _providers.Dispose(); + GC.SuppressFinalize(this); } public override async ValueTask DisposeAsync() @@ -55,10 +53,9 @@ namespace Kyoo.Controllers public override async Task Create(Library obj) { await base.Create(obj); - // _database.Entry(obj).State = EntityState.Added; - // _database.Entry(obj).Collection(x => x.Providers).IsModified = true; - // _database.Add(obj); - // var a = EF.Property>>(obj, nameof(LibraryDE.LibraryLinks)); + _database.Entry(obj).State = EntityState.Added; + obj.ProviderLinks = obj.Providers?.Select(x => Link.Create(obj, x)).ToArray(); + obj.ProviderLinks.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 0deecccf..c9521209 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -36,6 +36,7 @@ namespace Kyoo.Controllers _providers.Dispose(); if (_shows.IsValueCreated) _shows.Value.Dispose(); + GC.SuppressFinalize(this); } public override async ValueTask DisposeAsync() @@ -61,10 +62,7 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - if (obj.ExternalIDs != null) - foreach (MetadataID entry in obj.ExternalIDs) - _database.Entry(entry).State = EntityState.Added; - + obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 5fa057be..0ca44fdd 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -40,6 +40,7 @@ namespace Kyoo.Controllers _providers.Dispose(); if (_episodes.IsValueCreated) _episodes.Value.Dispose(); + GC.SuppressFinalize(this); } public override async ValueTask DisposeAsync() @@ -86,10 +87,7 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - if (obj.ExternalIDs != null) - foreach (MetadataID entry in obj.ExternalIDs) - _database.Entry(entry).State = EntityState.Added; - + obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 14a9e904..b7e29563 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -53,6 +53,7 @@ namespace Kyoo.Controllers _seasons.Value.Dispose(); if (_episodes.IsValueCreated) _episodes.Value.Dispose(); + GC.SuppressFinalize(this); } public override async ValueTask DisposeAsync() @@ -86,14 +87,10 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - - if (obj.People != null) - foreach (PeopleRole entry in obj.People) - _database.Entry(entry).State = EntityState.Added; - if (obj.ExternalIDs != null) - foreach (MetadataID entry in obj.ExternalIDs) - _database.Entry(entry).State = EntityState.Added; - + obj.GenreLinks = obj.Genres?.Select(x => Link.Create(obj, x)).ToArray(); + obj.GenreLinks.ForEach(x => _database.Entry(x).State = EntityState.Added); + obj.People.ForEach(x => _database.Entry(x).State = EntityState.Added); + obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 423bd3e7..250c381b 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -71,7 +71,7 @@ namespace Kyoo y => y .HasOne(x => x.Second) .WithMany(x => x.LibraryLinks), - y => y.HasKey(x => Link.PrimaryKey)); + y => y.HasKey(Link.PrimaryKey)); modelBuilder.Entity() .HasMany(x => x.Libraries) @@ -83,7 +83,7 @@ namespace Kyoo y => y .HasOne(x => x.Second) .WithMany(x => x.LibraryLinks), - y => y.HasKey(x => Link.PrimaryKey)); + y => y.HasKey(Link.PrimaryKey)); modelBuilder.Entity() .HasMany(x => x.Libraries) @@ -95,7 +95,7 @@ namespace Kyoo y => y .HasOne(x => x.Second) .WithMany(x => x.LibraryLinks), - y => y.HasKey(x => Link.PrimaryKey)); + y => y.HasKey(Link.PrimaryKey)); modelBuilder.Entity() .HasMany(x => x.Collections) @@ -107,7 +107,7 @@ namespace Kyoo y => y .HasOne(x => x.Second) .WithMany(x => x.CollectionLinks), - y => y.HasKey(x => Link.PrimaryKey)); + y => y.HasKey(Link.PrimaryKey)); modelBuilder.Entity() .HasMany(x => x.Shows) @@ -119,7 +119,7 @@ namespace Kyoo y => y .HasOne(x => x.Second) .WithMany(x => x.ShowLinks), - y => y.HasKey(x => Link.PrimaryKey)); + y => y.HasKey(Link.PrimaryKey)); modelBuilder.Entity() diff --git a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210306161631_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210306161631_Initial.Designer.cs new file mode 100644 index 00000000..5d406433 --- /dev/null +++ b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210306161631_Initial.Designer.cs @@ -0,0 +1,985 @@ +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration +{ + [DbContext(typeof(ConfigurationDbContext))] + [Migration("20210306161631_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AllowedAccessTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("LastAccessed") + .HasColumnType("timestamp without time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("NonEditable") + .HasColumnType("boolean"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + + b.Property("Updated") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Expiration") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Emphasize") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ScopeId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ScopeId"); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ScopeId") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ScopeId"); + + b.ToTable("ApiScopeProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AbsoluteRefreshTokenLifetime") + .HasColumnType("integer"); + + b.Property("AccessTokenLifetime") + .HasColumnType("integer"); + + b.Property("AccessTokenType") + .HasColumnType("integer"); + + b.Property("AllowAccessTokensViaBrowser") + .HasColumnType("boolean"); + + b.Property("AllowOfflineAccess") + .HasColumnType("boolean"); + + b.Property("AllowPlainTextPkce") + .HasColumnType("boolean"); + + b.Property("AllowRememberConsent") + .HasColumnType("boolean"); + + b.Property("AllowedIdentityTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("AlwaysIncludeUserClaimsInIdToken") + .HasColumnType("boolean"); + + b.Property("AlwaysSendClientClaims") + .HasColumnType("boolean"); + + b.Property("AuthorizationCodeLifetime") + .HasColumnType("integer"); + + b.Property("BackChannelLogoutSessionRequired") + .HasColumnType("boolean"); + + b.Property("BackChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ClientClaimsPrefix") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ConsentLifetime") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DeviceCodeLifetime") + .HasColumnType("integer"); + + b.Property("EnableLocalLogin") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("FrontChannelLogoutSessionRequired") + .HasColumnType("boolean"); + + b.Property("FrontChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IdentityTokenLifetime") + .HasColumnType("integer"); + + b.Property("IncludeJwtId") + .HasColumnType("boolean"); + + b.Property("LastAccessed") + .HasColumnType("timestamp without time zone"); + + b.Property("LogoUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("NonEditable") + .HasColumnType("boolean"); + + b.Property("PairWiseSubjectSalt") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RefreshTokenExpiration") + .HasColumnType("integer"); + + b.Property("RefreshTokenUsage") + .HasColumnType("integer"); + + b.Property("RequireClientSecret") + .HasColumnType("boolean"); + + b.Property("RequireConsent") + .HasColumnType("boolean"); + + b.Property("RequirePkce") + .HasColumnType("boolean"); + + b.Property("RequireRequestObject") + .HasColumnType("boolean"); + + b.Property("SlidingRefreshTokenLifetime") + .HasColumnType("integer"); + + b.Property("UpdateAccessTokenClaimsOnRefresh") + .HasColumnType("boolean"); + + b.Property("Updated") + .HasColumnType("timestamp without time zone"); + + b.Property("UserCodeType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UserSsoLifetime") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Origin") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientCorsOrigins"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientIdPRestrictions"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("PostLogoutRedirectUri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("RedirectUri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Expiration") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Emphasize") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("NonEditable") + .HasColumnType("boolean"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + + b.Property("Updated") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("IdentityResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("IdentityResourceId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("IdentityResourceId") + .HasColumnType("integer"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Properties") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("UserClaims") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("Properties") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("Properties") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Navigation("Properties"); + + b.Navigation("Scopes"); + + b.Navigation("Secrets"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Navigation("AllowedCorsOrigins"); + + b.Navigation("AllowedGrantTypes"); + + b.Navigation("AllowedScopes"); + + b.Navigation("Claims"); + + b.Navigation("ClientSecrets"); + + b.Navigation("IdentityProviderRestrictions"); + + b.Navigation("PostLogoutRedirectUris"); + + b.Navigation("Properties"); + + b.Navigation("RedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210306161631_Initial.cs b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210306161631_Initial.cs new file mode 100644 index 00000000..fec7b0f0 --- /dev/null +++ b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/20210306161631_Initial.cs @@ -0,0 +1,658 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApiResources", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Enabled = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + AllowedAccessTokenSigningAlgorithms = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false), + Updated = table.Column(type: "timestamp without time zone", nullable: true), + LastAccessed = table.Column(type: "timestamp without time zone", nullable: true), + NonEditable = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApiScopes", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Enabled = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + Required = table.Column(type: "boolean", nullable: false), + Emphasize = table.Column(type: "boolean", nullable: false), + ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Clients", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Enabled = table.Column(type: "boolean", nullable: false), + ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ProtocolType = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + RequireClientSecret = table.Column(type: "boolean", nullable: false), + ClientName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + ClientUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + LogoUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + RequireConsent = table.Column(type: "boolean", nullable: false), + AllowRememberConsent = table.Column(type: "boolean", nullable: false), + AlwaysIncludeUserClaimsInIdToken = table.Column(type: "boolean", nullable: false), + RequirePkce = table.Column(type: "boolean", nullable: false), + AllowPlainTextPkce = table.Column(type: "boolean", nullable: false), + RequireRequestObject = table.Column(type: "boolean", nullable: false), + AllowAccessTokensViaBrowser = table.Column(type: "boolean", nullable: false), + FrontChannelLogoutUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + FrontChannelLogoutSessionRequired = table.Column(type: "boolean", nullable: false), + BackChannelLogoutUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + BackChannelLogoutSessionRequired = table.Column(type: "boolean", nullable: false), + AllowOfflineAccess = table.Column(type: "boolean", nullable: false), + IdentityTokenLifetime = table.Column(type: "integer", nullable: false), + AllowedIdentityTokenSigningAlgorithms = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + AccessTokenLifetime = table.Column(type: "integer", nullable: false), + AuthorizationCodeLifetime = table.Column(type: "integer", nullable: false), + ConsentLifetime = table.Column(type: "integer", nullable: true), + AbsoluteRefreshTokenLifetime = table.Column(type: "integer", nullable: false), + SlidingRefreshTokenLifetime = table.Column(type: "integer", nullable: false), + RefreshTokenUsage = table.Column(type: "integer", nullable: false), + UpdateAccessTokenClaimsOnRefresh = table.Column(type: "boolean", nullable: false), + RefreshTokenExpiration = table.Column(type: "integer", nullable: false), + AccessTokenType = table.Column(type: "integer", nullable: false), + EnableLocalLogin = table.Column(type: "boolean", nullable: false), + IncludeJwtId = table.Column(type: "boolean", nullable: false), + AlwaysSendClientClaims = table.Column(type: "boolean", nullable: false), + ClientClaimsPrefix = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + PairWiseSubjectSalt = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Created = table.Column(type: "timestamp without time zone", nullable: false), + Updated = table.Column(type: "timestamp without time zone", nullable: true), + LastAccessed = table.Column(type: "timestamp without time zone", nullable: true), + UserSsoLifetime = table.Column(type: "integer", nullable: true), + UserCodeType = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + DeviceCodeLifetime = table.Column(type: "integer", nullable: false), + NonEditable = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Clients", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IdentityResources", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Enabled = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + Required = table.Column(type: "boolean", nullable: false), + Emphasize = table.Column(type: "boolean", nullable: false), + ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false), + Updated = table.Column(type: "timestamp without time zone", nullable: true), + NonEditable = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ApiResourceId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceClaims_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceProperties", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ApiResourceId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceProperties", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceProperties_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceScopes", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Scope = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ApiResourceId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceScopes", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceScopes_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceSecrets", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ApiResourceId = table.Column(type: "integer", nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + Value = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: false), + Expiration = table.Column(type: "timestamp without time zone", nullable: true), + Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceSecrets_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopeClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ScopeId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeClaims_ApiScopes_ScopeId", + column: x => x.ScopeId, + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopeProperties", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ScopeId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeProperties", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeProperties_ApiScopes_ScopeId", + column: x => x.ScopeId, + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientClaims", x => x.Id); + table.ForeignKey( + name: "FK_ClientClaims_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientCorsOrigins", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Origin = table.Column(type: "character varying(150)", maxLength: 150, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id); + table.ForeignKey( + name: "FK_ClientCorsOrigins_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientGrantTypes", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + GrantType = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientGrantTypes", x => x.Id); + table.ForeignKey( + name: "FK_ClientGrantTypes_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientIdPRestrictions", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Provider = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id); + table.ForeignKey( + name: "FK_ClientIdPRestrictions_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientPostLogoutRedirectUris", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PostLogoutRedirectUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientProperties", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientProperties", x => x.Id); + table.ForeignKey( + name: "FK_ClientProperties_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientRedirectUris", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RedirectUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientScopes", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Scope = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ClientId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientScopes", x => x.Id); + table.ForeignKey( + name: "FK_ClientScopes_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientSecrets", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + Description = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + Value = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: false), + Expiration = table.Column(type: "timestamp without time zone", nullable: true), + Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Created = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ClientSecrets_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityResourceClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + IdentityResourceId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResourceClaims", x => x.Id); + table.ForeignKey( + name: "FK_IdentityResourceClaims_IdentityResources_IdentityResourceId", + column: x => x.IdentityResourceId, + principalTable: "IdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityResourceProperties", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + IdentityResourceId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResourceProperties", x => x.Id); + table.ForeignKey( + name: "FK_IdentityResourceProperties_IdentityResources_IdentityResour~", + column: x => x.IdentityResourceId, + principalTable: "IdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceClaims_ApiResourceId", + table: "ApiResourceClaims", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceProperties_ApiResourceId", + table: "ApiResourceProperties", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResources_Name", + table: "ApiResources", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceScopes_ApiResourceId", + table: "ApiResourceScopes", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceSecrets_ApiResourceId", + table: "ApiResourceSecrets", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopeClaims_ScopeId", + table: "ApiScopeClaims", + column: "ScopeId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopeProperties_ScopeId", + table: "ApiScopeProperties", + column: "ScopeId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopes_Name", + table: "ApiScopes", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClientClaims_ClientId", + table: "ClientClaims", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientCorsOrigins_ClientId", + table: "ClientCorsOrigins", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientGrantTypes_ClientId", + table: "ClientGrantTypes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientIdPRestrictions_ClientId", + table: "ClientIdPRestrictions", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientPostLogoutRedirectUris_ClientId", + table: "ClientPostLogoutRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientProperties_ClientId", + table: "ClientProperties", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientRedirectUris_ClientId", + table: "ClientRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_Clients_ClientId", + table: "Clients", + column: "ClientId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClientScopes_ClientId", + table: "ClientScopes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientSecrets_ClientId", + table: "ClientSecrets", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResourceClaims_IdentityResourceId", + table: "IdentityResourceClaims", + column: "IdentityResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResourceProperties_IdentityResourceId", + table: "IdentityResourceProperties", + column: "IdentityResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResources_Name", + table: "IdentityResources", + column: "Name", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiResourceClaims"); + + migrationBuilder.DropTable( + name: "ApiResourceProperties"); + + migrationBuilder.DropTable( + name: "ApiResourceScopes"); + + migrationBuilder.DropTable( + name: "ApiResourceSecrets"); + + migrationBuilder.DropTable( + name: "ApiScopeClaims"); + + migrationBuilder.DropTable( + name: "ApiScopeProperties"); + + migrationBuilder.DropTable( + name: "ClientClaims"); + + migrationBuilder.DropTable( + name: "ClientCorsOrigins"); + + migrationBuilder.DropTable( + name: "ClientGrantTypes"); + + migrationBuilder.DropTable( + name: "ClientIdPRestrictions"); + + migrationBuilder.DropTable( + name: "ClientPostLogoutRedirectUris"); + + migrationBuilder.DropTable( + name: "ClientProperties"); + + migrationBuilder.DropTable( + name: "ClientRedirectUris"); + + migrationBuilder.DropTable( + name: "ClientScopes"); + + migrationBuilder.DropTable( + name: "ClientSecrets"); + + migrationBuilder.DropTable( + name: "IdentityResourceClaims"); + + migrationBuilder.DropTable( + name: "IdentityResourceProperties"); + + migrationBuilder.DropTable( + name: "ApiResources"); + + migrationBuilder.DropTable( + name: "ApiScopes"); + + migrationBuilder.DropTable( + name: "Clients"); + + migrationBuilder.DropTable( + name: "IdentityResources"); + } + } +} diff --git a/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs new file mode 100644 index 00000000..c11ac33a --- /dev/null +++ b/Kyoo/Models/DatabaseMigrations/IdentityConfiguration/ConfigurationDbContextModelSnapshot.cs @@ -0,0 +1,983 @@ +// +using System; +using IdentityServer4.EntityFramework.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration +{ + [DbContext(typeof(ConfigurationDbContext))] + partial class ConfigurationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AllowedAccessTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("LastAccessed") + .HasColumnType("timestamp without time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("NonEditable") + .HasColumnType("boolean"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + + b.Property("Updated") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiResourceId") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Expiration") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiResourceSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Emphasize") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ScopeId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ScopeId"); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("ScopeId") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ScopeId"); + + b.ToTable("ApiScopeProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AbsoluteRefreshTokenLifetime") + .HasColumnType("integer"); + + b.Property("AccessTokenLifetime") + .HasColumnType("integer"); + + b.Property("AccessTokenType") + .HasColumnType("integer"); + + b.Property("AllowAccessTokensViaBrowser") + .HasColumnType("boolean"); + + b.Property("AllowOfflineAccess") + .HasColumnType("boolean"); + + b.Property("AllowPlainTextPkce") + .HasColumnType("boolean"); + + b.Property("AllowRememberConsent") + .HasColumnType("boolean"); + + b.Property("AllowedIdentityTokenSigningAlgorithms") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("AlwaysIncludeUserClaimsInIdToken") + .HasColumnType("boolean"); + + b.Property("AlwaysSendClientClaims") + .HasColumnType("boolean"); + + b.Property("AuthorizationCodeLifetime") + .HasColumnType("integer"); + + b.Property("BackChannelLogoutSessionRequired") + .HasColumnType("boolean"); + + b.Property("BackChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ClientClaimsPrefix") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ConsentLifetime") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DeviceCodeLifetime") + .HasColumnType("integer"); + + b.Property("EnableLocalLogin") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("FrontChannelLogoutSessionRequired") + .HasColumnType("boolean"); + + b.Property("FrontChannelLogoutUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IdentityTokenLifetime") + .HasColumnType("integer"); + + b.Property("IncludeJwtId") + .HasColumnType("boolean"); + + b.Property("LastAccessed") + .HasColumnType("timestamp without time zone"); + + b.Property("LogoUri") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("NonEditable") + .HasColumnType("boolean"); + + b.Property("PairWiseSubjectSalt") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RefreshTokenExpiration") + .HasColumnType("integer"); + + b.Property("RefreshTokenUsage") + .HasColumnType("integer"); + + b.Property("RequireClientSecret") + .HasColumnType("boolean"); + + b.Property("RequireConsent") + .HasColumnType("boolean"); + + b.Property("RequirePkce") + .HasColumnType("boolean"); + + b.Property("RequireRequestObject") + .HasColumnType("boolean"); + + b.Property("SlidingRefreshTokenLifetime") + .HasColumnType("integer"); + + b.Property("UpdateAccessTokenClaimsOnRefresh") + .HasColumnType("boolean"); + + b.Property("Updated") + .HasColumnType("timestamp without time zone"); + + b.Property("UserCodeType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UserSsoLifetime") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Origin") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientCorsOrigins"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientIdPRestrictions"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("PostLogoutRedirectUri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("RedirectUri") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Expiration") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("DisplayName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Emphasize") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("NonEditable") + .HasColumnType("boolean"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("boolean"); + + b.Property("Updated") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("IdentityResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("IdentityResourceId") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("IdentityResourceId") + .HasColumnType("integer"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityResourceProperties"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Properties") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ApiResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("UserClaims") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") + .WithMany("Properties") + .HasForeignKey("ScopeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Scope"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("Properties") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("IdentityResource"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Navigation("Properties"); + + b.Navigation("Scopes"); + + b.Navigation("Secrets"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Navigation("AllowedCorsOrigins"); + + b.Navigation("AllowedGrantTypes"); + + b.Navigation("AllowedScopes"); + + b.Navigation("Claims"); + + b.Navigation("ClientSecrets"); + + b.Navigation("IdentityProviderRestrictions"); + + b.Navigation("PostLogoutRedirectUris"); + + b.Navigation("Properties"); + + b.Navigation("RedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Navigation("Properties"); + + b.Navigation("UserClaims"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs new file mode 100644 index 00000000..904bb2c2 --- /dev/null +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs @@ -0,0 +1,776 @@ +// +using System; +using Kyoo; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Kyoo.Models.DatabaseMigrations.Internal +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20210306181259_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) + .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) + .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("Kyoo.Models.Collection", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Collections"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AbsoluteNumber") + .HasColumnType("integer"); + + b.Property("EpisodeNumber") + .HasColumnType("integer"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("ReleaseDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Runtime") + .HasColumnType("integer"); + + b.Property("SeasonID") + .HasColumnType("integer"); + + b.Property("SeasonNumber") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("SeasonID"); + + b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") + .IsUnique(); + + b.ToTable("Episodes"); + }); + + modelBuilder.Entity("Kyoo.Models.Genre", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Genres"); + }); + + modelBuilder.Entity("Kyoo.Models.Library", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Paths") + .HasColumnType("text[]"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Libraries"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.MetadataID", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("DataID") + .HasColumnType("text"); + + b.Property("EpisodeID") + .HasColumnType("integer"); + + b.Property("Link") + .HasColumnType("text"); + + b.Property("PeopleID") + .HasColumnType("integer"); + + b.Property("ProviderID") + .HasColumnType("integer"); + + b.Property("SeasonID") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("EpisodeID"); + + b.HasIndex("PeopleID"); + + b.HasIndex("ProviderID"); + + b.HasIndex("SeasonID"); + + b.HasIndex("ShowID"); + + b.ToTable("MetadataIds"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("People"); + }); + + modelBuilder.Entity("Kyoo.Models.PeopleRole", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("PeopleID") + .HasColumnType("integer"); + + b.Property("Role") + .HasColumnType("text"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("PeopleID"); + + b.HasIndex("ShowID"); + + b.ToTable("PeopleRoles"); + }); + + modelBuilder.Entity("Kyoo.Models.ProviderID", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Logo") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Providers"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("SeasonNumber") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("ShowID", "SeasonNumber") + .IsUnique(); + + b.ToTable("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Aliases") + .HasColumnType("text[]"); + + b.Property("Backdrop") + .HasColumnType("text"); + + b.Property("EndYear") + .HasColumnType("integer"); + + b.Property("IsMovie") + .HasColumnType("boolean"); + + b.Property("Logo") + .HasColumnType("text"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartYear") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StudioID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("TrailerUrl") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("StudioID"); + + b.ToTable("Shows"); + }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Studios"); + }); + + modelBuilder.Entity("Kyoo.Models.Track", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Codec") + .HasColumnType("text"); + + b.Property("EpisodeID") + .HasColumnType("integer"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("IsForced") + .HasColumnType("boolean"); + + b.Property("Language") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("EpisodeID"); + + b.ToTable("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.HasOne("Kyoo.Models.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonID"); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("Episodes") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Collection", "First") + .WithMany("ShowLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Second") + .WithMany("CollectionLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("CollectionLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("ProviderLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.ProviderID", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("ShowLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Show", "First") + .WithMany("GenreLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Genre", "Second") + .WithMany("ShowLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.MetadataID", b => + { + b.HasOne("Kyoo.Models.Episode", "Episode") + .WithMany("ExternalIDs") + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.People", "People") + .WithMany("ExternalIDs") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.ProviderID", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", "Season") + .WithMany("ExternalIDs") + .HasForeignKey("SeasonID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("ExternalIDs") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("People"); + + b.Navigation("Provider"); + + b.Navigation("Season"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.PeopleRole", b => + { + b.HasOne("Kyoo.Models.People", "People") + .WithMany("Roles") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("People") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("People"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.HasOne("Kyoo.Models.Studio", "Studio") + .WithMany("Shows") + .HasForeignKey("StudioID"); + + b.Navigation("Studio"); + }); + + modelBuilder.Entity("Kyoo.Models.Track", b => + { + b.HasOne("Kyoo.Models.Episode", "Episode") + .WithMany("Tracks") + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("Kyoo.Models.Collection", b => + { + b.Navigation("LibraryLinks"); + + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.Genre", b => + { + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Library", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("ProviderLinks"); + + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Kyoo.Models.ProviderID", b => + { + b.Navigation("LibraryLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + + b.Navigation("GenreLinks"); + + b.Navigation("LibraryLinks"); + + b.Navigation("People"); + + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Navigation("Shows"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs new file mode 100644 index 00000000..c9e95676 --- /dev/null +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs @@ -0,0 +1,604 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Kyoo.Models.DatabaseMigrations.Internal +{ + 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", "finished,airing,planned,unknown") + .Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,font"); + + 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), + Poster = table.Column(type: "text", 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), + Poster = table.Column(type: "text", 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), + Logo = table.Column(type: "text", 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: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Collections_SecondID", + column: x => x.SecondID, + principalTable: "Collections", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Libraries_FirstID", + column: x => x.FirstID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Libraries_FirstID", + column: x => x.FirstID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Providers_SecondID", + column: x => x.SecondID, + 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: "integer", nullable: true), + TrailerUrl = table.Column(type: "text", nullable: true), + StartYear = table.Column(type: "integer", nullable: true), + EndYear = table.Column(type: "integer", nullable: true), + Poster = table.Column(type: "text", nullable: true), + Logo = table.Column(type: "text", nullable: true), + Backdrop = table.Column(type: "text", nullable: true), + IsMovie = table.Column(type: "boolean", nullable: false), + StudioID = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Shows", x => x.ID); + table.ForeignKey( + name: "FK_Shows_Studios_StudioID", + column: x => x.StudioID, + principalTable: "Studios", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Collections_FirstID", + column: x => x.FirstID, + principalTable: "Collections", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Shows_SecondID", + column: x => x.SecondID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Libraries_FirstID", + column: x => x.FirstID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Shows_SecondID", + column: x => x.SecondID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Genres_SecondID", + column: x => x.SecondID, + principalTable: "Genres", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Shows_FirstID", + column: x => x.FirstID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PeopleRoles", + columns: table => new + { + ID = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PeopleID = table.Column(type: "integer", nullable: false), + ShowID = table.Column(type: "integer", nullable: false), + Role = table.Column(type: "text", nullable: true), + Type = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PeopleRoles", x => x.ID); + table.ForeignKey( + name: "FK_PeopleRoles_People_PeopleID", + column: x => x.PeopleID, + principalTable: "People", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PeopleRoles_Shows_ShowID", + column: x => x.ShowID, + 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), + ShowID = table.Column(type: "integer", nullable: false), + SeasonNumber = table.Column(type: "integer", nullable: false), + Title = table.Column(type: "text", nullable: true), + Overview = table.Column(type: "text", nullable: true), + Year = table.Column(type: "integer", nullable: true), + Poster = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Seasons", x => x.ID); + table.ForeignKey( + name: "FK_Seasons_Shows_ShowID", + column: x => x.ShowID, + 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), + ShowID = table.Column(type: "integer", nullable: false), + SeasonID = table.Column(type: "integer", nullable: true), + SeasonNumber = table.Column(type: "integer", nullable: false), + EpisodeNumber = table.Column(type: "integer", nullable: false), + AbsoluteNumber = table.Column(type: "integer", nullable: false), + Path = table.Column(type: "text", nullable: true), + Title = table.Column(type: "text", nullable: true), + Overview = table.Column(type: "text", nullable: true), + ReleaseDate = table.Column(type: "timestamp without time zone", nullable: true), + Runtime = table.Column(type: "integer", nullable: false), + Poster = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Episodes", x => x.ID); + table.ForeignKey( + name: "FK_Episodes_Seasons_SeasonID", + column: x => x.SeasonID, + principalTable: "Seasons", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Episodes_Shows_ShowID", + column: x => x.ShowID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MetadataIds", + columns: table => new + { + ID = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ProviderID = table.Column(type: "integer", nullable: false), + ShowID = table.Column(type: "integer", nullable: true), + EpisodeID = table.Column(type: "integer", nullable: true), + SeasonID = table.Column(type: "integer", nullable: true), + PeopleID = table.Column(type: "integer", nullable: true), + DataID = table.Column(type: "text", nullable: true), + Link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MetadataIds", x => x.ID); + table.ForeignKey( + name: "FK_MetadataIds_Episodes_EpisodeID", + column: x => x.EpisodeID, + principalTable: "Episodes", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MetadataIds_People_PeopleID", + column: x => x.PeopleID, + principalTable: "People", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MetadataIds_Providers_ProviderID", + column: x => x.ProviderID, + principalTable: "Providers", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MetadataIds_Seasons_SeasonID", + column: x => x.SeasonID, + principalTable: "Seasons", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MetadataIds_Shows_ShowID", + column: x => x.ShowID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Tracks", + columns: table => new + { + ID = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EpisodeID = table.Column(type: "integer", nullable: false), + IsDefault = table.Column(type: "boolean", nullable: false), + IsForced = table.Column(type: "boolean", nullable: false), + IsExternal = table.Column(type: "boolean", nullable: false), + Title = table.Column(type: "text", nullable: true), + Language = table.Column(type: "text", nullable: true), + Codec = table.Column(type: "text", nullable: true), + Path = table.Column(type: "text", nullable: true), + Type = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tracks", x => x.ID); + table.ForeignKey( + name: "FK_Tracks_Episodes_EpisodeID", + column: x => x.EpisodeID, + principalTable: "Episodes", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Collections_Slug", + table: "Collections", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Episodes_SeasonID", + table: "Episodes", + column: "SeasonID"); + + migrationBuilder.CreateIndex( + name: "IX_Episodes_ShowID_SeasonNumber_EpisodeNumber_AbsoluteNumber", + table: "Episodes", + columns: new[] { "ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber" }, + 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_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_Link_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_Link_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_Link_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_Link_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_EpisodeID", + table: "MetadataIds", + column: "EpisodeID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_PeopleID", + table: "MetadataIds", + column: "PeopleID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_ProviderID", + table: "MetadataIds", + column: "ProviderID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_SeasonID", + table: "MetadataIds", + column: "SeasonID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_ShowID", + table: "MetadataIds", + column: "ShowID"); + + migrationBuilder.CreateIndex( + name: "IX_People_Slug", + table: "People", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PeopleRoles_PeopleID", + table: "PeopleRoles", + column: "PeopleID"); + + migrationBuilder.CreateIndex( + name: "IX_PeopleRoles_ShowID", + table: "PeopleRoles", + column: "ShowID"); + + migrationBuilder.CreateIndex( + name: "IX_Providers_Slug", + table: "Providers", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Seasons_ShowID_SeasonNumber", + table: "Seasons", + columns: new[] { "ShowID", "SeasonNumber" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Shows_Slug", + table: "Shows", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Shows_StudioID", + table: "Shows", + column: "StudioID"); + + migrationBuilder.CreateIndex( + name: "IX_Studios_Slug", + table: "Studios", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Tracks_EpisodeID", + table: "Tracks", + column: "EpisodeID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "MetadataIds"); + + migrationBuilder.DropTable( + name: "PeopleRoles"); + + migrationBuilder.DropTable( + name: "Tracks"); + + migrationBuilder.DropTable( + name: "Collections"); + + migrationBuilder.DropTable( + name: "Libraries"); + + migrationBuilder.DropTable( + name: "Genres"); + + migrationBuilder.DropTable( + name: "Providers"); + + migrationBuilder.DropTable( + name: "People"); + + migrationBuilder.DropTable( + name: "Episodes"); + + migrationBuilder.DropTable( + name: "Seasons"); + + migrationBuilder.DropTable( + name: "Shows"); + + migrationBuilder.DropTable( + name: "Studios"); + } + } +} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs new file mode 100644 index 00000000..5f5a7395 --- /dev/null +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -0,0 +1,774 @@ +// +using System; +using Kyoo; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Kyoo.Models.DatabaseMigrations.Internal +{ + [DbContext(typeof(DatabaseContext))] + partial class DatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) + .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) + .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("Kyoo.Models.Collection", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Collections"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AbsoluteNumber") + .HasColumnType("integer"); + + b.Property("EpisodeNumber") + .HasColumnType("integer"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("ReleaseDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Runtime") + .HasColumnType("integer"); + + b.Property("SeasonID") + .HasColumnType("integer"); + + b.Property("SeasonNumber") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("SeasonID"); + + b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") + .IsUnique(); + + b.ToTable("Episodes"); + }); + + modelBuilder.Entity("Kyoo.Models.Genre", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Genres"); + }); + + modelBuilder.Entity("Kyoo.Models.Library", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Paths") + .HasColumnType("text[]"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Libraries"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.MetadataID", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("DataID") + .HasColumnType("text"); + + b.Property("EpisodeID") + .HasColumnType("integer"); + + b.Property("Link") + .HasColumnType("text"); + + b.Property("PeopleID") + .HasColumnType("integer"); + + b.Property("ProviderID") + .HasColumnType("integer"); + + b.Property("SeasonID") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("EpisodeID"); + + b.HasIndex("PeopleID"); + + b.HasIndex("ProviderID"); + + b.HasIndex("SeasonID"); + + b.HasIndex("ShowID"); + + b.ToTable("MetadataIds"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("People"); + }); + + modelBuilder.Entity("Kyoo.Models.PeopleRole", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("PeopleID") + .HasColumnType("integer"); + + b.Property("Role") + .HasColumnType("text"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("PeopleID"); + + b.HasIndex("ShowID"); + + b.ToTable("PeopleRoles"); + }); + + modelBuilder.Entity("Kyoo.Models.ProviderID", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Logo") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Providers"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("SeasonNumber") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("ShowID", "SeasonNumber") + .IsUnique(); + + b.ToTable("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Aliases") + .HasColumnType("text[]"); + + b.Property("Backdrop") + .HasColumnType("text"); + + b.Property("EndYear") + .HasColumnType("integer"); + + b.Property("IsMovie") + .HasColumnType("boolean"); + + b.Property("Logo") + .HasColumnType("text"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartYear") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StudioID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("TrailerUrl") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("StudioID"); + + b.ToTable("Shows"); + }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Studios"); + }); + + modelBuilder.Entity("Kyoo.Models.Track", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Codec") + .HasColumnType("text"); + + b.Property("EpisodeID") + .HasColumnType("integer"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("IsForced") + .HasColumnType("boolean"); + + b.Property("Language") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("EpisodeID"); + + b.ToTable("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.HasOne("Kyoo.Models.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonID"); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("Episodes") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Collection", "First") + .WithMany("ShowLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Second") + .WithMany("CollectionLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("CollectionLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("ProviderLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.ProviderID", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("ShowLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Show", "First") + .WithMany("GenreLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Genre", "Second") + .WithMany("ShowLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.MetadataID", b => + { + b.HasOne("Kyoo.Models.Episode", "Episode") + .WithMany("ExternalIDs") + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.People", "People") + .WithMany("ExternalIDs") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.ProviderID", "Provider") + .WithMany() + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", "Season") + .WithMany("ExternalIDs") + .HasForeignKey("SeasonID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("ExternalIDs") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("People"); + + b.Navigation("Provider"); + + b.Navigation("Season"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.PeopleRole", b => + { + b.HasOne("Kyoo.Models.People", "People") + .WithMany("Roles") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("People") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("People"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.HasOne("Kyoo.Models.Studio", "Studio") + .WithMany("Shows") + .HasForeignKey("StudioID"); + + b.Navigation("Studio"); + }); + + modelBuilder.Entity("Kyoo.Models.Track", b => + { + b.HasOne("Kyoo.Models.Episode", "Episode") + .WithMany("Tracks") + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("Kyoo.Models.Collection", b => + { + b.Navigation("LibraryLinks"); + + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.Genre", b => + { + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Library", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("ProviderLinks"); + + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Kyoo.Models.ProviderID", b => + { + b.Navigation("LibraryLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + + b.Navigation("GenreLinks"); + + b.Navigation("LibraryLinks"); + + b.Navigation("People"); + + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Navigation("Shows"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index ff7f9868..2d65987d 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -75,6 +75,10 @@ namespace Kyoo.Controllers ICollection libraries = argument == null ? await libraryManager.GetLibraries() : new [] { await libraryManager.GetLibrary(argument)}; + + if (argument != null && libraries.First() == null) + throw new ArgumentException($"No library found with the name {argument}"); + foreach (Library library in libraries) await libraryManager.Load(library, x => x.Providers); From 6bf67463200e65753c5c4069c45a014401209015 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 6 Mar 2021 22:26:39 +0100 Subject: [PATCH 24/54] Fixing one to one relation null's serialization --- .../Implementations/LibraryManager.cs | 31 +++++++++++++++---- .../Models/Attributes/RelationAttributes.cs | 14 +++++++-- Kyoo.Common/Models/Resources/Episode.cs | 8 ++--- Kyoo.Common/Models/Resources/Season.cs | 4 +-- Kyoo.Common/Models/Resources/Show.cs | 2 +- Kyoo.Common/Models/Resources/Track.cs | 13 ++++++-- Kyoo.Common/Utility.cs | 14 +++++++++ Kyoo.CommonAPI/JsonSerializer.cs | 18 +++++++++-- Kyoo.CommonAPI/ResourceViewAttribute.cs | 1 - 9 files changed, 83 insertions(+), 22 deletions(-) diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index c14f1251..6fbffa72 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -306,10 +306,13 @@ namespace Kyoo.Controllers (Show s, nameof(Show.Collections)) => CollectionRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Collections = x), - // TODO Studio loading does not work. (Show s, nameof(Show.Studio)) => StudioRepository .Get(x => x.Shows.Any(y => y.ID == obj.ID)) - .Then(x => s.Studio = x), + .Then(x => + { + s.Studio = x; + s.StudioID = x?.ID ?? 0; + }), (Season s, nameof(Season.ExternalIDs)) => ProviderRepository .GetMetadataID(x => x.SeasonID == obj.ID) @@ -319,7 +322,11 @@ namespace Kyoo.Controllers .Then(x => s.Episodes = x), (Season s, nameof(Season.Show)) => ShowRepository .Get(x => x.Seasons.Any(y => y.ID == obj.ID)) - .Then(x => s.Show = x), + .Then(x => + { + s.Show = x; + s.ShowID = x?.ID ?? 0; + }), (Episode e, nameof(Episode.ExternalIDs)) => ProviderRepository .GetMetadataID(x => x.EpisodeID == obj.ID) @@ -329,14 +336,26 @@ namespace Kyoo.Controllers .Then(x => e.Tracks = x), (Episode e, nameof(Episode.Show)) => ShowRepository .Get(x => x.Episodes.Any(y => y.ID == obj.ID)) - .Then(x => e.Show = x), + .Then(x => + { + e.Show = x; + e.ShowID = x?.ID ?? 0; + }), (Episode e, nameof(Episode.Season)) => SeasonRepository .Get(x => x.Episodes.Any(y => y.ID == e.ID)) - .Then(x => e.Season = x), + .Then(x => + { + e.Season = x; + e.SeasonID = x?.ID ?? 0; + }), (Track t, nameof(Track.Episode)) => EpisodeRepository .Get(x => x.Tracks.Any(y => y.ID == obj.ID)) - .Then(x => t.Episode = x), + .Then(x => + { + t.Episode = x; + t.EpisodeID = x?.ID ?? 0; + }), (Genre g, nameof(Genre.Shows)) => ShowRepository .GetAll(x => x.Genres.Any(y => y.ID == obj.ID)) diff --git a/Kyoo.Common/Models/Attributes/RelationAttributes.cs b/Kyoo.Common/Models/Attributes/RelationAttributes.cs index 36edbbf2..aac0e633 100644 --- a/Kyoo.Common/Models/Attributes/RelationAttributes.cs +++ b/Kyoo.Common/Models/Attributes/RelationAttributes.cs @@ -4,7 +4,17 @@ namespace Kyoo.Models.Attributes { [AttributeUsage(AttributeTargets.Property, Inherited = false)] public class EditableRelationAttribute : Attribute { } - + [AttributeUsage(AttributeTargets.Property)] - public class LoadableRelationAttribute : Attribute { } + public class LoadableRelationAttribute : Attribute + { + public string RelationID { get; } + + public LoadableRelationAttribute() {} + + public LoadableRelationAttribute(string relationID) + { + RelationID = relationID; + } + } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 9ef8c684..42863a5d 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -10,10 +10,10 @@ namespace Kyoo.Models { public int ID { get; set; } public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString(); - public int ShowID { get; set; } - [LoadableRelation] public virtual Show Show { get; set; } - public int? SeasonID { get; set; } - [LoadableRelation] public virtual Season Season { get; set; } + [SerializeIgnore] public int ShowID { get; set; } + [LoadableRelation(nameof(ShowID))] public virtual Show Show { get; set; } + [SerializeIgnore] public int? SeasonID { get; set; } + [LoadableRelation(nameof(SeasonID))] public virtual Season Season { get; set; } public int SeasonNumber { get; set; } = -1; public int EpisodeNumber { get; set; } = -1; diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index ecaff2e4..adba3b9f 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -8,7 +8,7 @@ namespace Kyoo.Models public class Season : IResource { public int ID { get; set; } - public int ShowID { get; set; } + [SerializeIgnore] public int ShowID { get; set; } public int SeasonNumber { get; set; } = -1; @@ -21,7 +21,7 @@ namespace Kyoo.Models public string Thumb => $"/api/seasons/{Slug}/thumb"; [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [LoadableRelation] public virtual Show Show { get; set; } + [LoadableRelation(nameof(ShowID))] public virtual Show Show { get; set; } [LoadableRelation] public virtual ICollection Episodes { get; set; } public Season() { } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 520cfeb7..a3cda2dd 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -28,7 +28,7 @@ namespace Kyoo.Models [SerializeIgnore] public int? StudioID { get; set; } - [LoadableRelation] [EditableRelation] public virtual Studio Studio { get; set; } + [LoadableRelation(nameof(StudioID))] [EditableRelation] public virtual Studio Studio { get; set; } [LoadableRelation] [EditableRelation] public virtual ICollection Genres { get; set; } [LoadableRelation] [EditableRelation] public virtual ICollection People { get; set; } [LoadableRelation] public virtual ICollection Seasons { get; set; } diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index f4b82fc7..7895bba6 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -57,7 +57,7 @@ namespace Kyoo.Models public class Track : Stream, IResource { public int ID { get; set; } - public int EpisodeID { get; set; } + [SerializeIgnore] public int EpisodeID { get; set; } public bool IsDefault { get => isDefault; @@ -114,11 +114,18 @@ namespace Kyoo.Models } public bool IsExternal { get; set; } - [LoadableRelation] public virtual Episode Episode { get; set; } + [LoadableRelation(nameof(EpisodeID))] public virtual Episode Episode { get; set; } public Track() { } - public Track(StreamType type, string title, string language, bool isDefault, bool isForced, string codec, bool isExternal, string path) + public Track(StreamType type, + string title, + string language, + bool isDefault, + bool isForced, + string codec, + bool isExternal, + string path) : base(title, language, codec, isDefault, isForced, path, type) { IsExternal = isExternal; diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index f50d7018..521abc11 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -33,6 +33,20 @@ namespace Kyoo : ex.Body as MemberExpression; return member!.Member.Name; } + + 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}).") + }; + } public static string ToSlug(string str) { diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index d0d30975..df200c26 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -10,9 +10,21 @@ namespace Kyoo.Controllers protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); - - if (member?.GetCustomAttribute() != null) - property.NullValueHandling = NullValueHandling.Ignore; + + LoadableRelationAttribute relation = member?.GetCustomAttribute(); + if (relation != null) + { + if (relation.RelationID == null) + property.ShouldSerialize = x => member.GetValue(x) != null; + else + property.ShouldSerialize = x => + { + if (member.GetValue(x) != null) + return true; + return x.GetType().GetProperty(relation.RelationID)?.GetValue(x) != null; + }; + } + if (member?.GetCustomAttribute() != null) property.ShouldSerialize = _ => false; if (member?.GetCustomAttribute() != null) diff --git a/Kyoo.CommonAPI/ResourceViewAttribute.cs b/Kyoo.CommonAPI/ResourceViewAttribute.cs index 7a60da55..a05a8fec 100644 --- a/Kyoo.CommonAPI/ResourceViewAttribute.cs +++ b/Kyoo.CommonAPI/ResourceViewAttribute.cs @@ -75,7 +75,6 @@ namespace Kyoo.CommonApi Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>)); - // TODO loading is case sensitive. Maybe convert them in the first check. if (pageType != null) { foreach (IResource resource in ((dynamic)result.Value).Items) From 9a7b2cb4a1d4521beed333a02a50594024f2c661 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 7 Mar 2021 01:56:22 +0100 Subject: [PATCH 25/54] Loading a relation set the inverse relation too but json serialization encounters infinite loops. --- .../Implementations/LibraryManager.cs | 92 ++++++++++++++----- Kyoo.Common/Models/WatchItem.cs | 13 +-- Kyoo/Views/WebClient | 2 +- 3 files changed, 75 insertions(+), 32 deletions(-) diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 6fbffa72..9974dab8 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -261,6 +261,14 @@ namespace Kyoo.Controllers return obj; } + private async Task SetRelation(T1 obj, Task> loader, Action> setter, Action inverse) + { + ICollection loaded = await loader; + setter(obj, loaded); + foreach (T2 item in loaded) + inverse(item, obj); + } + public Task Load(IResource obj, string member) { if (obj == null) @@ -270,42 +278,57 @@ namespace Kyoo.Controllers { (Library l, nameof(Library.Providers)) => ProviderRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) - .Then(x => l.Providers = x), + .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), + (Library l, nameof(Library.Collections)) => CollectionRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) .Then(x => l.Collections = x), + (Collection c, nameof(Library.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.ExternalIDs)) => ProviderRepository - .GetMetadataID(x => x.ShowID == obj.ID) - .Then(x => s.ExternalIDs = x), + + (Show s, nameof(Show.ExternalIDs)) => SetRelation(s, + ProviderRepository.GetMetadataID(x => x.ShowID == obj.ID), + (x, y) => x.ExternalIDs = y, + (x, y) => x.Show = y), + (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), - (Show s, nameof(Show.Seasons)) => SeasonRepository - .GetAll(x => x.Show.ID == obj.ID) - .Then(x => s.Seasons = x), - (Show s, nameof(Show.Episodes)) => EpisodeRepository - .GetAll(x => x.Show.ID == obj.ID) - .Then(x => s.Episodes = x), + + (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; }), + + (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; }), + (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), + (Show s, nameof(Show.Studio)) => StudioRepository .Get(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => @@ -314,12 +337,17 @@ namespace Kyoo.Controllers s.StudioID = x?.ID ?? 0; }), - (Season s, nameof(Season.ExternalIDs)) => ProviderRepository - .GetMetadataID(x => x.SeasonID == obj.ID) - .Then(x => s.ExternalIDs = x), - (Season s, nameof(Season.Episodes)) => EpisodeRepository - .GetAll(x => x.Season.ID == obj.ID) - .Then(x => s.Episodes = x), + + (Season s, nameof(Season.ExternalIDs)) => SetRelation(s, + ProviderRepository.GetMetadataID(x => x.SeasonID == obj.ID), + (x, y) => x.ExternalIDs = y, + (x, y) => { x.Season = y; x.SeasonID = y.ID; }), + + (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; }), + (Season s, nameof(Season.Show)) => ShowRepository .Get(x => x.Seasons.Any(y => y.ID == obj.ID)) .Then(x => @@ -328,12 +356,17 @@ namespace Kyoo.Controllers s.ShowID = x?.ID ?? 0; }), - (Episode e, nameof(Episode.ExternalIDs)) => ProviderRepository - .GetMetadataID(x => x.EpisodeID == obj.ID) - .Then(x => e.ExternalIDs = x), - (Episode e, nameof(Episode.Tracks)) => TrackRepository - .GetAll(x => x.Episode.ID == obj.ID) - .Then(x => e.Tracks = x), + + (Episode e, nameof(Episode.ExternalIDs)) => SetRelation(e, + ProviderRepository.GetMetadataID(x => x.EpisodeID == obj.ID), + (x, y) => x.ExternalIDs = y, + (x, y) => { x.Episode = y; x.EpisodeID = y.ID; }), + + (Episode e, nameof(Episode.Tracks)) => SetRelation(e, + TrackRepository.GetAll(x => x.Episode.ID == obj.ID), + (x, y) => x.Tracks = y, + (x, y) => { x.Episode = y; x.EpisodeID = y.ID; }), + (Episode e, nameof(Episode.Show)) => ShowRepository .Get(x => x.Episodes.Any(y => y.ID == obj.ID)) .Then(x => @@ -341,6 +374,7 @@ namespace Kyoo.Controllers e.Show = x; e.ShowID = x?.ID ?? 0; }), + (Episode e, nameof(Episode.Season)) => SeasonRepository .Get(x => x.Episodes.Any(y => y.ID == e.ID)) .Then(x => @@ -349,6 +383,7 @@ namespace Kyoo.Controllers e.SeasonID = x?.ID ?? 0; }), + (Track t, nameof(Track.Episode)) => EpisodeRepository .Get(x => x.Tracks.Any(y => y.ID == obj.ID)) .Then(x => @@ -357,24 +392,31 @@ namespace Kyoo.Controllers t.EpisodeID = x?.ID ?? 0; }), + (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), - (People p, nameof(People.ExternalIDs)) => ProviderRepository - .GetMetadataID(x => x.PeopleID == obj.ID) - .Then(x => p.ExternalIDs = x), + + (People p, nameof(People.ExternalIDs)) => SetRelation(p, + ProviderRepository.GetMetadataID(x => x.PeopleID == obj.ID), + (x, y) => x.ExternalIDs = y, + (x, y) => { x.People = y; x.PeopleID = y.ID; }), + (People p, nameof(People.Roles)) => PeopleRepository .GetFromPeople(obj.ID) .Then(x => p.Roles = x), + (ProviderID p, nameof(ProviderID.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 {member} of {obj.Slug}.") }; diff --git a/Kyoo.Common/Models/WatchItem.cs b/Kyoo.Common/Models/WatchItem.cs index e007e04c..0fe617da 100644 --- a/Kyoo.Common/Models/WatchItem.cs +++ b/Kyoo.Common/Models/WatchItem.cs @@ -89,7 +89,7 @@ namespace Kyoo.Models public static async Task FromEpisode(Episode ep, ILibraryManager library) { - Show show = await library.GetShow(ep.ShowID); // TODO load only the title, the slug & the IsMovie with the library manager. + Show show = await library.GetShow(ep.ShowID); Episode previous = null; Episode next = null; @@ -110,6 +110,7 @@ namespace Kyoo.Models next = await library.GetEpisode(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber + 1); } + await library.Load(ep, x => x.Tracks); return new WatchItem(ep.ID, show.Title, show.Slug, @@ -118,9 +119,9 @@ namespace Kyoo.Models ep.Title, ep.ReleaseDate, ep.Path, - await library.GetTrack(x => x.EpisodeID == ep.ID && x.Type == StreamType.Video), - await library.GetTracks(x => x.EpisodeID == ep.ID && x.Type == StreamType.Audio), - await library.GetTracks(x => x.EpisodeID == ep.ID && x.Type == StreamType.Subtitle)) + ep.Tracks.FirstOrDefault(x => x.Type == StreamType.Video), + ep.Tracks.Where(x => x.Type == StreamType.Audio), + ep.Tracks.Where(x => x.Type == StreamType.Subtitle)) { IsMovie = show.IsMovie, PreviousEpisode = previous, @@ -137,7 +138,7 @@ namespace Kyoo.Models PathIO.GetFileNameWithoutExtension(episodePath) + ".txt" ); if (!File.Exists(path)) - return new Chapter[0]; + return Array.Empty(); try { return (await File.ReadAllLinesAsync(path)) @@ -151,7 +152,7 @@ namespace Kyoo.Models catch { await Console.Error.WriteLineAsync($"Invalid chapter file at {path}"); - return new Chapter[0]; + return Array.Empty(); } } } diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index 09edd091..35e6a601 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit 09edd091b9bc75b697da4dc16eeaf9aadb9d4b05 +Subproject commit 35e6a601edc494d1bea142fd419c7750c18d21d9 From fab9a3f6a14ac74e1f82989757df3f7a01b333e5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 7 Mar 2021 18:15:14 +0100 Subject: [PATCH 26/54] Fixing nested serializing --- Kyoo.CommonAPI/JsonSerializer.cs | 15 ++++++++++++++- .../Controllers/Repositories/EpisodeRepository.cs | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index df200c26..71f9c6e6 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -1,3 +1,4 @@ +using System; using System.Reflection; using Kyoo.Models.Attributes; using Newtonsoft.Json; @@ -7,6 +8,8 @@ namespace Kyoo.Controllers { public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver { + private int _depth = -1; + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); @@ -15,10 +18,12 @@ namespace Kyoo.Controllers if (relation != null) { if (relation.RelationID == null) - property.ShouldSerialize = x => member.GetValue(x) != null; + property.ShouldSerialize = x => _depth == 0 && member.GetValue(x) != null; else property.ShouldSerialize = x => { + if (_depth != 0) + return false; if (member.GetValue(x) != null) return true; return x.GetType().GetProperty(relation.RelationID)?.GetValue(x) != null; @@ -31,5 +36,13 @@ namespace Kyoo.Controllers property.ShouldDeserialize = _ => false; return property; } + + protected override JsonContract CreateContract(Type objectType) + { + JsonContract contract = base.CreateContract(objectType); + contract.OnSerializingCallbacks.Add((_, _) => _depth++); + contract.OnSerializedCallbacks.Add((_, _) => _depth--); + return contract; + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 816b2232..c4552d63 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -46,7 +46,7 @@ namespace Kyoo.Controllers public override Task Get(string slug) { - Match match = Regex.Match(slug, @"(?.*)-s(?\d*)-e(?\d*)"); + Match match = Regex.Match(slug, @"(?.*)-s(?\d*)e(?\d*)"); if (!match.Success) return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug); From b3fdee4bcd783380c3c74022c6a346b9135e3c9b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 7 Mar 2021 21:20:03 +0100 Subject: [PATCH 27/54] Fixing composed slug handling & page field serialization --- Kyoo.Common/Controllers/IRepository.cs | 2 + Kyoo.Common/Models/Attributes/ComposedSlug.cs | 7 -- Kyoo.Common/Models/Resources/Episode.cs | 10 +-- Kyoo.Common/Models/Resources/Season.cs | 6 +- Kyoo.Common/Models/WatchItem.cs | 18 ++-- Kyoo.CommonAPI/JsonSerializer.cs | 11 ++- Kyoo.CommonAPI/LocalRepository.cs | 2 +- .../Repositories/EpisodeRepository.cs | 90 ++++++++++++++----- .../Repositories/SeasonRepository.cs | 53 +++++++++-- .../Repositories/ShowRepository.cs | 7 ++ .../Repositories/TrackRepository.cs | 2 +- 11 files changed, 152 insertions(+), 56 deletions(-) delete mode 100644 Kyoo.Common/Models/Attributes/ComposedSlug.cs diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 6ed573dc..4e8b2440 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -109,6 +109,8 @@ namespace Kyoo.Controllers public interface IShowRepository : IRepository { Task AddShowLink(int showID, int? libraryID, int? collectionID); + + Task GetSlug(int showID); } public interface ISeasonRepository : IRepository diff --git a/Kyoo.Common/Models/Attributes/ComposedSlug.cs b/Kyoo.Common/Models/Attributes/ComposedSlug.cs deleted file mode 100644 index 4595d5a4..00000000 --- a/Kyoo.Common/Models/Attributes/ComposedSlug.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace Kyoo.Models.Attributes -{ - [AttributeUsage(AttributeTargets.Class)] - public class ComposedSlugAttribute : Attribute { } -} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 42863a5d..4d45c397 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -5,11 +5,11 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { - [ComposedSlug] public class Episode : IResource, IOnMerge { public int ID { get; set; } - public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString(); + public string Slug => GetSlug(ShowSlug, SeasonNumber, EpisodeNumber); + [SerializeIgnore] public string ShowSlug { private get; set; } [SerializeIgnore] public int ShowID { get; set; } [LoadableRelation(nameof(ShowID))] public virtual Show Show { get; set; } [SerializeIgnore] public int? SeasonID { get; set; } @@ -30,9 +30,7 @@ namespace Kyoo.Models [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } [LoadableRelation] public virtual ICollection Tracks { get; set; } - - public string ShowTitle => Show?.Title; - + public Episode() { } @@ -78,6 +76,8 @@ namespace Kyoo.Models public static string GetSlug(string showSlug, int seasonNumber, int episodeNumber) { + if (showSlug == null) + throw new ArgumentException("Show's slug is null. Can't find episode's slug."); if (seasonNumber == -1) return showSlug; return $"{showSlug}-s{seasonNumber}e{episodeNumber}"; diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index adba3b9f..8691e5f3 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -4,15 +4,16 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { - [ComposedSlug] public class Season : IResource { public int ID { get; set; } + public string Slug => $"{ShowSlug}-s{SeasonNumber}"; [SerializeIgnore] public int ShowID { get; set; } + [SerializeIgnore] public string ShowSlug { private get; set; } + [LoadableRelation(nameof(ShowID))] public virtual Show Show { get; set; } public int SeasonNumber { get; set; } = -1; - public string Slug => Show != null ? $"{Show.Slug}-s{SeasonNumber}" : ID.ToString(); public string Title { get; set; } public string Overview { get; set; } public int? Year { get; set; } @@ -21,7 +22,6 @@ namespace Kyoo.Models public string Thumb => $"/api/seasons/{Slug}/thumb"; [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [LoadableRelation(nameof(ShowID))] public virtual Show Show { get; set; } [LoadableRelation] public virtual ICollection Episodes { get; set; } public Season() { } diff --git a/Kyoo.Common/Models/WatchItem.cs b/Kyoo.Common/Models/WatchItem.cs index 0fe617da..f552efce 100644 --- a/Kyoo.Common/Models/WatchItem.cs +++ b/Kyoo.Common/Models/WatchItem.cs @@ -25,7 +25,7 @@ namespace Kyoo.Models public class WatchItem { - public readonly int EpisodeID = -1; + public readonly int EpisodeID; public string ShowTitle; public string ShowSlug; @@ -41,9 +41,9 @@ namespace Kyoo.Models public string Container; public Track Video; - public IEnumerable Audios; - public IEnumerable Subtitles; - public IEnumerable Chapters; + public ICollection Audios; + public ICollection Subtitles; + public ICollection Chapters; public WatchItem() { } @@ -78,8 +78,8 @@ namespace Kyoo.Models DateTime? releaseDate, string path, Track video, - IEnumerable audios, - IEnumerable subtitles) + ICollection audios, + ICollection subtitles) : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path) { Video = video; @@ -120,8 +120,8 @@ namespace Kyoo.Models ep.ReleaseDate, ep.Path, ep.Tracks.FirstOrDefault(x => x.Type == StreamType.Video), - ep.Tracks.Where(x => x.Type == StreamType.Audio), - ep.Tracks.Where(x => x.Type == StreamType.Subtitle)) + ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(), + ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray()) { IsMovie = show.IsMovie, PreviousEpisode = previous, @@ -130,7 +130,7 @@ namespace Kyoo.Models }; } - private static async Task> GetChapters(string episodePath) + private static async Task> GetChapters(string episodePath) { string path = PathIO.Combine( PathIO.GetDirectoryName(episodePath)!, diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 71f9c6e6..69d818ae 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -1,5 +1,7 @@ using System; +using System.Collections; using System.Reflection; +using Kyoo.Models; using Kyoo.Models.Attributes; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -40,8 +42,13 @@ namespace Kyoo.Controllers protected override JsonContract CreateContract(Type objectType) { JsonContract contract = base.CreateContract(objectType); - contract.OnSerializingCallbacks.Add((_, _) => _depth++); - contract.OnSerializedCallbacks.Add((_, _) => _depth--); + if (Utility.GetGenericDefinition(objectType, typeof(Page<>)) == null + && !objectType.IsAssignableTo(typeof(IEnumerable))) + { + contract.OnSerializingCallbacks.Add((_, _) => _depth++); + contract.OnSerializedCallbacks.Add((_, _) => _depth--); + } + return contract; } } diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 0e47c5b3..d5c7f790 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -209,7 +209,7 @@ namespace Kyoo.Controllers { if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("Resource can't have null as a slug."); - if (int.TryParse(resource.Slug, out int _) && typeof(T).GetCustomAttribute() == null) + if (int.TryParse(resource.Slug, out int _)) { try { diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index c4552d63..ab1c1df3 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -5,7 +5,6 @@ using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; -using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers @@ -15,13 +14,16 @@ namespace Kyoo.Controllers private bool _disposed; private readonly DatabaseContext _database; private readonly IProviderRepository _providers; + private readonly IShowRepository _shows; protected override Expression> DefaultSort => x => x.EpisodeNumber; - public EpisodeRepository(DatabaseContext database, IProviderRepository providers) : base(database) + public EpisodeRepository(DatabaseContext database, IProviderRepository providers, IShowRepository shows) + : base(database) { _database = database; _providers = providers; + _shows = shows; } @@ -32,6 +34,7 @@ namespace Kyoo.Controllers _disposed = true; _database.Dispose(); _providers.Dispose(); + _shows.Dispose(); GC.SuppressFinalize(this); } @@ -42,6 +45,15 @@ namespace Kyoo.Controllers _disposed = true; await _database.DisposeAsync(); await _providers.DisposeAsync(); + await _shows.DisposeAsync(); + } + + public override async Task Get(int id) + { + Episode ret = await base.Get(id); + if (ret != null) + ret.ShowSlug = await _shows.GetSlug(ret.ShowID); + return ret; } public override Task Get(string slug) @@ -54,45 +66,81 @@ namespace Kyoo.Controllers int.Parse(match.Groups["season"].Value), int.Parse(match.Groups["episode"].Value)); } - - public Task Get(string showSlug, int seasonNumber, int episodeNumber) + + public override async Task Get(Expression> predicate) { - return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug - && x.SeasonNumber == seasonNumber - && x.EpisodeNumber == episodeNumber); + Episode ret = await base.Get(predicate); + if (ret != null) + ret.ShowSlug = await _shows.GetSlug(ret.ShowID); + return ret; } - public Task Get(int showID, int seasonNumber, int episodeNumber) + public async Task Get(string showSlug, int seasonNumber, int episodeNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID - && x.SeasonNumber == seasonNumber - && x.EpisodeNumber == episodeNumber); + Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + && x.SeasonNumber == seasonNumber + && x.EpisodeNumber == episodeNumber); + if (ret != null) + ret.ShowSlug = showSlug; + return ret; } - public Task Get(int seasonID, int episodeNumber) + public async Task Get(int showID, int seasonNumber, int episodeNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.SeasonID == seasonID - && x.EpisodeNumber == episodeNumber); + Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + && x.SeasonNumber == seasonNumber + && x.EpisodeNumber == episodeNumber); + if (ret != null) + ret.ShowSlug = await _shows.GetSlug(showID); + return ret; } - public Task GetAbsolute(int showID, int absoluteNumber) + public async Task Get(int seasonID, int episodeNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID - && x.AbsoluteNumber == absoluteNumber); + Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.SeasonID == seasonID + && x.EpisodeNumber == episodeNumber); + if (ret != null) + ret.ShowSlug = await _shows.GetSlug(ret.ShowID); + return ret; } - public Task GetAbsolute(string showSlug, int absoluteNumber) + public async Task GetAbsolute(int showID, int absoluteNumber) { - return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug - && x.AbsoluteNumber == absoluteNumber); + Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + && x.AbsoluteNumber == absoluteNumber); + if (ret != null) + ret.ShowSlug = await _shows.GetSlug(showID); + return ret; + } + + public async Task GetAbsolute(string showSlug, int absoluteNumber) + { + Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + && x.AbsoluteNumber == absoluteNumber); + if (ret != null) + ret.ShowSlug = showSlug; + return ret; } public override async Task> Search(string query) { - return await _database.Episodes + List episodes = await _database.Episodes .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) .Take(20) .ToListAsync(); + foreach (Episode episode in episodes) + episode.ShowSlug = await _shows.GetSlug(episode.ShowID); + return episodes; + } + + public override async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection episodes = await base.GetAll(where, sort, limit); + foreach (Episode episode in episodes) + episode.ShowSlug = await _shows.GetSlug(episode.ShowID); + return episodes; } public override async Task Create(Episode obj) diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 0ca44fdd..6fa7a298 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -5,7 +5,6 @@ using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; -using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -16,17 +15,20 @@ namespace Kyoo.Controllers private bool _disposed; private readonly DatabaseContext _database; private readonly IProviderRepository _providers; + private readonly IShowRepository _shows; private readonly Lazy _episodes; protected override Expression> DefaultSort => x => x.SeasonNumber; public SeasonRepository(DatabaseContext database, IProviderRepository providers, + IShowRepository shows, IServiceProvider services) : base(database) { _database = database; _providers = providers; + _shows = shows; _episodes = new Lazy(services.GetRequiredService); } @@ -38,6 +40,7 @@ namespace Kyoo.Controllers _disposed = true; _database.Dispose(); _providers.Dispose(); + _shows.Dispose(); if (_episodes.IsValueCreated) _episodes.Value.Dispose(); GC.SuppressFinalize(this); @@ -50,10 +53,27 @@ namespace Kyoo.Controllers _disposed = true; await _database.DisposeAsync(); await _providers.DisposeAsync(); + await _shows.DisposeAsync(); if (_episodes.IsValueCreated) await _episodes.Value.DisposeAsync(); } + public override async Task Get(int id) + { + Season ret = await base.Get(id); + if (ret != null) + ret.ShowSlug = await _shows.GetSlug(ret.ShowID); + return ret; + } + + public override async Task Get(Expression> predicate) + { + Season ret = await base.Get(predicate); + if (ret != null) + ret.ShowSlug = await _shows.GetSlug(ret.ShowID); + return ret; + } + public override Task Get(string slug) { Match match = Regex.Match(slug, @"(?.*)-s(?\d*)"); @@ -63,24 +83,43 @@ namespace Kyoo.Controllers return Get(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value)); } - public Task Get(int showID, int seasonNumber) + public async Task Get(int showID, int seasonNumber) { - return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID - && x.SeasonNumber == seasonNumber); + Season ret = await _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID + && x.SeasonNumber == seasonNumber); + if (ret != null) + ret.ShowSlug = await _shows.GetSlug(showID); + return ret; } - public Task Get(string showSlug, int seasonNumber) + public async Task Get(string showSlug, int seasonNumber) { - return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + Season ret = await _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug && x.SeasonNumber == seasonNumber); + if (ret != null) + ret.ShowSlug = showSlug; + return ret; } public override async Task> Search(string query) { - return await _database.Seasons + List seasons = await _database.Seasons .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) .Take(20) .ToListAsync(); + foreach (Season season in seasons) + season.ShowSlug = await _shows.GetSlug(season.ShowID); + return seasons; + } + + public override async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection seasons = await base.GetAll(where, sort, limit); + foreach (Season season in seasons) + season.ShowSlug = await _shows.GetSlug(season.ShowID); + return seasons; } public override async Task Create(Season obj) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index b7e29563..cc0b2749 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -151,6 +151,13 @@ namespace Kyoo.Controllers } } + public Task GetSlug(int showID) + { + return _database.Shows.Where(x => x.ID == showID) + .Select(x => x.Slug) + .FirstOrDefaultAsync(); + } + public override async Task Delete(Show obj) { if (obj == null) diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 1008dab7..413b202f 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -82,7 +82,7 @@ namespace Kyoo.Controllers { if (obj.EpisodeID <= 0) { - obj.EpisodeID = obj.Episode?.ID ?? -1; + obj.EpisodeID = obj.Episode?.ID ?? 0; if (obj.EpisodeID <= 0) throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID})."); } From d3d2677a83cc73c5a6a1c8e2b06bfff2694e9fe9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 8 Mar 2021 23:53:46 +0100 Subject: [PATCH 28/54] Creating a custom json serializer for PeopleRoles --- Kyoo.Common/Models/PeopleRole.cs | 4 ---- Kyoo.CommonAPI/JsonSerializer.cs | 21 +++++++++++++++++++++ Kyoo/Startup.cs | 6 +++++- Kyoo/Views/API/LibraryItemApi.cs | 1 + 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Kyoo.Common/Models/PeopleRole.cs b/Kyoo.Common/Models/PeopleRole.cs index 533f0fca..fe027682 100644 --- a/Kyoo.Common/Models/PeopleRole.cs +++ b/Kyoo.Common/Models/PeopleRole.cs @@ -1,7 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; using Kyoo.Models.Attributes; namespace Kyoo.Models @@ -12,7 +9,6 @@ namespace Kyoo.Models [SerializeIgnore] public string Slug => ForPeople ? Show.Slug : People.Slug; [SerializeIgnore] public bool ForPeople; [SerializeIgnore] public int PeopleID { get; set; } - // TODO implement a SerializeInline for People or Show depending on the context. [SerializeIgnore] public virtual People People { get; set; } [SerializeIgnore] public int ShowID { get; set; } [SerializeIgnore] public virtual Show Show { get; set; } diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 69d818ae..420ab9b8 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -4,6 +4,7 @@ using System.Reflection; using Kyoo.Models; using Kyoo.Models.Attributes; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; namespace Kyoo.Controllers @@ -52,4 +53,24 @@ namespace Kyoo.Controllers return contract; } } + + public class PeopleRoleConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer) + { + // TODO this seems to not use the property.ShouldSerialize and cause an recursive inclusion error. + JToken t = JToken.FromObject(value, serializer); + JObject obj = t as JObject; + writer.WriteValue(obj); + } + + public override PeopleRole ReadJson(JsonReader reader, + Type objectType, + PeopleRole existingValue, + bool hasExistingValue, + JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } } \ No newline at end of file diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index b04a7f18..c31c51e6 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -43,7 +43,11 @@ namespace Kyoo }); services.AddControllers() - .AddNewtonsoftJson(x => x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer()); + .AddNewtonsoftJson(x => + { + x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(); + x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); + }); services.AddHttpClient(); services.AddDbContext(options => diff --git a/Kyoo/Views/API/LibraryItemApi.cs b/Kyoo/Views/API/LibraryItemApi.cs index e0ae3560..9f97a275 100644 --- a/Kyoo/Views/API/LibraryItemApi.cs +++ b/Kyoo/Views/API/LibraryItemApi.cs @@ -15,6 +15,7 @@ namespace Kyoo.Api [Route("api/item")] [Route("api/items")] [ApiController] + [ResourceView] public class LibraryItemApi : ControllerBase { private readonly ILibraryItemRepository _libraryItems; From 356b8a5472116d17ff7edca0e33a5bc3057ace49 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 10 Mar 2021 00:46:17 +0100 Subject: [PATCH 29/54] Cleaning up searches --- Kyoo.CommonAPI/JsonSerializer.cs | 21 ++++++++++++--- .../Repositories/CollectionRepository.cs | 1 + .../Repositories/EpisodeRepository.cs | 3 ++- .../Repositories/GenreRepository.cs | 1 + .../Repositories/LibraryItemRepository.cs | 1 + .../Repositories/LibraryRepository.cs | 1 + .../Repositories/PeopleRepository.cs | 26 +++++++++++++------ .../Repositories/ProviderRepository.cs | 1 + .../Repositories/SeasonRepository.cs | 1 + .../Repositories/ShowRepository.cs | 1 + .../Repositories/StudioRepository.cs | 1 + 11 files changed, 45 insertions(+), 13 deletions(-) diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 420ab9b8..57eb45d9 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Reflection; using Kyoo.Models; using Kyoo.Models.Attributes; @@ -58,10 +59,22 @@ namespace Kyoo.Controllers { public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer) { - // TODO this seems to not use the property.ShouldSerialize and cause an recursive inclusion error. - JToken t = JToken.FromObject(value, serializer); - JObject obj = t as JObject; - writer.WriteValue(obj); + ICollection oldPeople = value.Show?.People; + ICollection oldRoles = value.People?.Roles; + if (value.Show != null) + value.Show.People = null; + if (value.People != null) + value.People.Roles = null; + + JObject obj = JObject.FromObject(value.ForPeople ? value.People : value.Show, serializer); + obj.Add("role", value.Role); + obj.Add("type", value.Type); + obj.WriteTo(writer); + + if (value.Show != null) + value.Show.People = oldPeople; + if (value.People != null) + value.People.Roles = oldRoles; } public override PeopleRole ReadJson(JsonReader reader, diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 44623818..ccebc298 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -40,6 +40,7 @@ namespace Kyoo.Controllers { return await _database.Collections .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index ab1c1df3..222e18de 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -125,7 +125,8 @@ namespace Kyoo.Controllers public override async Task> Search(string query) { List episodes = await _database.Episodes - .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) + .Where(x => EF.Functions.ILike(x.Title, $"%{query}%") && x.EpisodeNumber != -1) + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); foreach (Episode episode in episodes) diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index d6e8363b..bfbc1e3c 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -41,6 +41,7 @@ namespace Kyoo.Controllers { return await _database.Genres .Where(genre => EF.Functions.ILike(genre.Name, $"%{query}%")) + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 8d32d19b..20f7139f 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -96,6 +96,7 @@ namespace Kyoo.Controllers { return await ItemsQuery .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 96a09ac7..9278c563 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -46,6 +46,7 @@ namespace Kyoo.Controllers { return await _database.Libraries .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index c9521209..d5b15fea 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -54,6 +54,7 @@ namespace Kyoo.Controllers { return await _database.People .Where(people => EF.Functions.ILike(people.Name, $"%{query}%")) + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); } @@ -97,7 +98,9 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - ICollection people = await ApplyFilters(_database.PeopleRoles.Where(x => x.ShowID == showID), + ICollection people = await ApplyFilters(_database.PeopleRoles + .Where(x => x.ShowID == showID) + .Include(x => x.People), id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), x => x.People.Name, where, @@ -105,6 +108,8 @@ namespace Kyoo.Controllers limit); if (!people.Any() && await _shows.Value.Get(showID) == null) throw new ItemNotFound(); + foreach (PeopleRole role in people) + role.ForPeople = true; return people; } @@ -113,7 +118,10 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - ICollection people = await ApplyFilters(_database.PeopleRoles.Where(x => x.Show.Slug == showSlug), + ICollection people = await ApplyFilters(_database.PeopleRoles + .Where(x => x.Show.Slug == showSlug) + .Include(x => x.People) + .Include(x => x.Show), id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), x => x.People.Name, where, @@ -121,6 +129,8 @@ namespace Kyoo.Controllers limit); if (!people.Any() && await _shows.Value.Get(showSlug) == null) throw new ItemNotFound(); + foreach (PeopleRole role in people) + role.ForPeople = true; return people; } @@ -129,7 +139,9 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - ICollection roles = await ApplyFilters(_database.PeopleRoles.Where(x => x.PeopleID == peopleID), + ICollection roles = await ApplyFilters(_database.PeopleRoles + .Where(x => x.PeopleID == peopleID) + .Include(x => x.Show), id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), x => x.Show.Title, where, @@ -137,8 +149,6 @@ namespace Kyoo.Controllers limit); if (!roles.Any() && await Get(peopleID) == null) throw new ItemNotFound(); - foreach (PeopleRole role in roles) - role.ForPeople = true; return roles; } @@ -147,7 +157,9 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - ICollection roles = await ApplyFilters(_database.PeopleRoles.Where(x => x.People.Slug == slug), + ICollection roles = await ApplyFilters(_database.PeopleRoles + .Where(x => x.People.Slug == slug) + .Include(x => x.Show), id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), x => x.Show.Title, where, @@ -155,8 +167,6 @@ namespace Kyoo.Controllers limit); if (!roles.Any() && await Get(slug) == null) throw new ItemNotFound(); - foreach (PeopleRole role in roles) - role.ForPeople = true; return roles; } } diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 1d229921..72f4d354 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -23,6 +23,7 @@ namespace Kyoo.Controllers { return await _database.Providers .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 6fa7a298..c1344ed6 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -105,6 +105,7 @@ namespace Kyoo.Controllers { List seasons = await _database.Seasons .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); foreach (Season season in seasons) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index cc0b2749..545dd880 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -79,6 +79,7 @@ namespace Kyoo.Controllers .Where(x => EF.Functions.ILike(x.Title, query) || EF.Functions.ILike(x.Slug, query) /*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE. + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index d020de24..623e5090 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -23,6 +23,7 @@ namespace Kyoo.Controllers { return await _database.Studios .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) + .OrderBy(DefaultSort) .Take(20) .ToListAsync(); } From 779702f96903f16d2e9ecb51d7d4aed7f8183968 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 13 Mar 2021 00:49:12 +0100 Subject: [PATCH 30/54] Serializing images paths --- .../Models/Attributes/SerializeAttribute.cs | 11 +++ Kyoo.Common/Models/Resources/Collection.cs | 2 +- Kyoo.Common/Models/Resources/Episode.cs | 8 +- Kyoo.Common/Models/Resources/Season.cs | 3 +- Kyoo.Common/Models/Resources/Show.cs | 6 +- Kyoo.CommonAPI/JsonSerializer.cs | 50 +++++++++- Kyoo.CommonAPI/ResourceViewAttribute.cs | 15 ++- Kyoo/Controllers/ProviderManager.cs | 4 +- Kyoo/Controllers/ThumbnailsManager.cs | 4 +- ....cs => 20210312234147_Initial.Designer.cs} | 8 +- ...9_Initial.cs => 20210312234147_Initial.cs} | 4 +- .../Internal/DatabaseContextModelSnapshot.cs | 6 +- Kyoo/Program.cs | 1 + Kyoo/Startup.cs | 5 +- Kyoo/Tasks/Crawler.cs | 3 +- Kyoo/Views/API/ThumbnailAPI.cs | 94 ------------------- 16 files changed, 100 insertions(+), 124 deletions(-) rename Kyoo/Models/DatabaseMigrations/Internal/{20210306181259_Initial.Designer.cs => 20210312234147_Initial.Designer.cs} (99%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210306181259_Initial.cs => 20210312234147_Initial.cs} (99%) delete mode 100644 Kyoo/Views/API/ThumbnailAPI.cs diff --git a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs index 3622f77d..3eafb90c 100644 --- a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs +++ b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs @@ -7,4 +7,15 @@ namespace Kyoo.Models.Attributes [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class DeserializeIgnoreAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class SerializeAsAttribute : Attribute + { + public string Format { get; } + + public SerializeAsAttribute(string format) + { + Format = format; + } + } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 936620c9..b1274278 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -8,7 +8,7 @@ namespace Kyoo.Models public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } - public string Poster { get; set; } + [SerializeAs("{HOST}/api/library/{Slug}/poster")] public string Poster { get; set; } public string Overview { get; set; } [LoadableRelation] public virtual ICollection Shows { get; set; } [LoadableRelation] public virtual ICollection Libraries { get; set; } diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 4d45c397..0515da46 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -19,14 +19,14 @@ namespace Kyoo.Models public int EpisodeNumber { get; set; } = -1; public int AbsoluteNumber { get; set; } = -1; [SerializeIgnore] public string Path { get; set; } - public string Thumb => $"/api/episodes/{Slug}/thumb"; + + [SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] public string Thumb { get; set; } public string Title { get; set; } public string Overview { get; set; } public DateTime? ReleaseDate { get; set; } public int Runtime { get; set; } //This runtime variable should be in minutes - [SerializeIgnore] public string Poster { get; set; } [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } [LoadableRelation] public virtual ICollection Tracks { get; set; } @@ -41,7 +41,7 @@ namespace Kyoo.Models string overview, DateTime? releaseDate, int runtime, - string poster, + string thumb, IEnumerable externalIDs) { SeasonNumber = seasonNumber; @@ -51,7 +51,7 @@ namespace Kyoo.Models Overview = overview; ReleaseDate = releaseDate; Runtime = runtime; - Poster = poster; + Thumb = thumb; ExternalIDs = externalIDs?.ToArray(); } diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 8691e5f3..827b24f7 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -18,8 +18,7 @@ namespace Kyoo.Models public string Overview { get; set; } public int? Year { get; set; } - [SerializeIgnore] public string Poster { get; set; } - public string Thumb => $"/api/seasons/{Slug}/thumb"; + [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; } [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } [LoadableRelation] public virtual ICollection Episodes { get; set; } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index a3cda2dd..7b948b55 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -18,9 +18,9 @@ namespace Kyoo.Models public int? StartYear { get; set; } public int? EndYear { get; set; } - public string Poster { get; set; } - public string Logo { get; set; } - public string Backdrop { get; set; } + [SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; } + [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; } public bool IsMovie { get; set; } diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 57eb45d9..8702e66b 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -1,7 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using Kyoo.Models; using Kyoo.Models.Attributes; using Newtonsoft.Json; @@ -13,7 +15,13 @@ namespace Kyoo.Controllers public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver { private int _depth = -1; - + private string _host; + + public JsonPropertyIgnorer(string host) + { + _host = host; + } + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); @@ -38,6 +46,10 @@ namespace Kyoo.Controllers property.ShouldSerialize = _ => false; if (member?.GetCustomAttribute() != null) property.ShouldDeserialize = _ => false; + + SerializeAsAttribute serializeAs = member?.GetCustomAttribute(); + if (serializeAs != null) + property.ValueProvider = new SerializeAsProvider(serializeAs.Format, _host); return property; } @@ -86,4 +98,40 @@ namespace Kyoo.Controllers throw new NotImplementedException(); } } + + public class SerializeAsProvider : IValueProvider + { + private string _format; + private string _host; + + public SerializeAsProvider(string format, string host) + { + _format = format; + _host = host.TrimEnd('/'); + } + + public object GetValue(object target) + { + return Regex.Replace(_format, @"(? + { + string value = x.Groups[1].Value; + + if (value == "HOST") + return _host; + + PropertyInfo properties = target.GetType().GetProperties() + .FirstOrDefault(y => y.Name == value); + if (properties == null) + return null; + if (properties.GetValue(target) is string ret) + return ret; + throw new ArgumentException($"Invalid serializer replacement {value}"); + }); + } + + public void SetValue(object target, object value) + { + throw new NotImplementedException(); + } + } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/ResourceViewAttribute.cs b/Kyoo.CommonAPI/ResourceViewAttribute.cs index a05a8fec..fa177342 100644 --- a/Kyoo.CommonAPI/ResourceViewAttribute.cs +++ b/Kyoo.CommonAPI/ResourceViewAttribute.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Kyoo.CommonApi { @@ -24,9 +25,15 @@ namespace Kyoo.CommonApi where.Remove(key); } - string[] fields = context.HttpContext.Request.Query["fields"] + List fields = context.HttpContext.Request.Query["fields"] .SelectMany(x => x.Split(',')) - .ToArray(); + .ToList(); + if (fields.Contains("internal")) + { + fields.Remove("internal"); + context.HttpContext.Items["internal"] = true; + // TODO disable SerializeAs attributes when this is true. + } if (context.ActionDescriptor is ControllerActionDescriptor descriptor) { Type type = descriptor.MethodInfo.ReturnType; @@ -50,7 +57,7 @@ namespace Kyoo.CommonApi }); return null; }) - .ToArray(); + .ToList(); if (context.Result != null) return; } @@ -71,7 +78,7 @@ namespace Kyoo.CommonApi return; await using ILibraryManager library = context.HttpContext.RequestServices.GetService(); - string[] fields = (string[])context.HttpContext.Items["fields"]; + ICollection fields = (ICollection)context.HttpContext.Items["fields"]; Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>)); diff --git a/Kyoo/Controllers/ProviderManager.cs b/Kyoo/Controllers/ProviderManager.cs index b2795c85..d025c31c 100644 --- a/Kyoo/Controllers/ProviderManager.cs +++ b/Kyoo/Controllers/ProviderManager.cs @@ -44,7 +44,7 @@ namespace Kyoo.Controllers Library library, string what) { - List ret = new List(); + List ret = new(); IEnumerable providers = library?.Providers .Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug)) @@ -121,6 +121,7 @@ namespace Kyoo.Controllers $"the season {seasonNumber} of {show.Title}"); season.Show = show; season.ShowID = show.ID; + season.ShowSlug = show.Slug; season.SeasonNumber = season.SeasonNumber == -1 ? seasonNumber : season.SeasonNumber; season.Title ??= $"Season {season.SeasonNumber}"; return season; @@ -139,6 +140,7 @@ namespace Kyoo.Controllers "an episode"); episode.Show = show; episode.ShowID = show.ID; + episode.ShowSlug = show.Slug; episode.Path = episodePath; episode.SeasonNumber = episode.SeasonNumber != -1 ? episode.SeasonNumber : seasonNumber; episode.EpisodeNumber = episode.EpisodeNumber != -1 ? episode.EpisodeNumber : episodeNumber; diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 07a1d709..ac4aeeec 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -96,11 +96,11 @@ namespace Kyoo.Controllers if (episode?.Path == null) return default; - if (episode.Poster != null) + if (episode.Thumb != null) { string localPath = Path.ChangeExtension(episode.Path, "jpg"); if (alwaysDownload || !File.Exists(localPath)) - await DownloadImage(episode.Poster, localPath, $"The thumbnail of {episode.Show.Title}"); + await DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Show.Title}"); } return episode; } diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs index 904bb2c2..8817046e 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210306181259_Initial")] + [Migration("20210312234147_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -71,9 +71,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Path") .HasColumnType("text"); - b.Property("Poster") - .HasColumnType("text"); - b.Property("ReleaseDate") .HasColumnType("timestamp without time zone"); @@ -89,6 +86,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("ShowID") .HasColumnType("integer"); + b.Property("Thumb") + .HasColumnType("text"); + b.Property("Title") .HasColumnType("text"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs index c9e95676..4b007b7f 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs @@ -318,11 +318,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal EpisodeNumber = table.Column(type: "integer", nullable: false), AbsoluteNumber = table.Column(type: "integer", nullable: false), Path = table.Column(type: "text", nullable: true), + Thumb = table.Column(type: "text", nullable: true), Title = table.Column(type: "text", nullable: true), Overview = table.Column(type: "text", nullable: true), ReleaseDate = table.Column(type: "timestamp without time zone", nullable: true), - Runtime = table.Column(type: "integer", nullable: false), - Poster = table.Column(type: "text", nullable: true) + Runtime = table.Column(type: "integer", nullable: false) }, constraints: table => { diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 5f5a7395..fb3f4599 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -69,9 +69,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Path") .HasColumnType("text"); - b.Property("Poster") - .HasColumnType("text"); - b.Property("ReleaseDate") .HasColumnType("timestamp without time zone"); @@ -87,6 +84,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("ShowID") .HasColumnType("integer"); + b.Property("Thumb") + .HasColumnType("text"); + b.Property("Title") .HasColumnType("text"); diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index eae316f2..5659843d 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -11,6 +11,7 @@ namespace Kyoo { public static async Task Main(string[] args) { + if (args.Length > 0) FileSystem.CurrentDirectory = args[0]; if (!File.Exists("./appsettings.json")) diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index c31c51e6..72666c57 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -37,6 +37,8 @@ namespace Kyoo public void ConfigureServices(IServiceCollection services) { + string publicUrl = _configuration.GetValue("public_url"); + services.AddSpaStaticFiles(configuration => { configuration.RootPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"); @@ -45,7 +47,7 @@ namespace Kyoo services.AddControllers() .AddNewtonsoftJson(x => { - x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(); + x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(publicUrl); x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); }); services.AddHttpClient(); @@ -63,7 +65,6 @@ namespace Kyoo }); string assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; - string publicUrl = _configuration.GetValue("public_url"); services.AddIdentityCore(o => { diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 2d65987d..0cbf431a 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -351,7 +351,8 @@ namespace Kyoo.Controllers Title = show.Title, Path = episodePath, Show = show, - ShowID = show.ID + ShowID = show.ID, + ShowSlug = show.Slug }; episode.Tracks = await GetTracks(episode); return episode; diff --git a/Kyoo/Views/API/ThumbnailAPI.cs b/Kyoo/Views/API/ThumbnailAPI.cs deleted file mode 100644 index 72c13ae0..00000000 --- a/Kyoo/Views/API/ThumbnailAPI.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System.IO; -using System.Threading.Tasks; -using Kyoo.Controllers; -using Microsoft.AspNetCore.Authorization; - -namespace Kyoo.Api -{ - public class ThumbnailController : ControllerBase - { - private readonly ILibraryManager _libraryManager; - private readonly string _peoplePath; - - - public ThumbnailController(ILibraryManager libraryManager, IConfiguration config) - { - _libraryManager = libraryManager; - _peoplePath = config.GetValue("peoplePath"); - } - - - [HttpGet("poster/{showSlug}")] - [Authorize(Policy="Read")] - public async Task GetShowThumb(string showSlug) - { - string path = (await _libraryManager.GetShow(showSlug))?.Path; - if (path == null) - return NotFound(); - - string thumb = Path.Combine(path, "poster.jpg"); - - if (System.IO.File.Exists(thumb)) - return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); - return NotFound(); - } - - [HttpGet("logo/{showSlug}")] - [Authorize(Policy="Read")] - public async Task GetShowLogo(string showSlug) - { - string path = (await _libraryManager.GetShow(showSlug))?.Path; - if (path == null) - return NotFound(); - - string thumb = Path.Combine(path, "logo.png"); - - if (System.IO.File.Exists(thumb)) - return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); - return NotFound(); - } - - [HttpGet("backdrop/{showSlug}")] - [Authorize(Policy="Read")] - public async Task GetShowBackdrop(string showSlug) - { - string path = (await _libraryManager.GetShow(showSlug))?.Path; - if (path == null) - return NotFound(); - - string thumb = Path.Combine(path, "backdrop.jpg"); - - if (System.IO.File.Exists(thumb)) - return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); - return NotFound(); - } - - [HttpGet("peopleimg/{peopleSlug}")] - [Authorize(Policy="Read")] - public IActionResult GetPeopleIcon(string peopleSlug) - { - string thumbPath = Path.Combine(_peoplePath, peopleSlug + ".jpg"); - if (!System.IO.File.Exists(thumbPath)) - return NotFound(); - - return new PhysicalFileResult(Path.GetFullPath(thumbPath), "image/jpg"); - } - - [HttpGet("thumb/{showSlug}-s{seasonNumber}e{episodeNumber}")] - [Authorize(Policy="Read")] - public async Task GetEpisodeThumb(string showSlug, int seasonNumber, int episodeNumber) - { - string path = (await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber))?.Path; - if (path == null) - return NotFound(); - - string thumb = Path.ChangeExtension(path, "jpg"); - - if (System.IO.File.Exists(thumb)) - return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); - return NotFound(); - } - } -} From 42ff22ad131afff8dcc2a8ea597fede55807c3e9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 13 Mar 2021 22:51:45 +0100 Subject: [PATCH 31/54] Finishing to rework image's path serialization --- .../Implementations/LibraryManager.cs | 9 ++- Kyoo.Common/Models/LibraryItem.cs | 4 +- Kyoo.Common/Models/Resources/People.cs | 2 +- Kyoo.Common/Models/WatchItem.cs | 68 ++++++++++--------- Kyoo.CommonAPI/JsonSerializer.cs | 3 +- .../Repositories/ProviderRepository.cs | 2 +- Kyoo/Views/API/PeopleApi.cs | 2 +- Kyoo/Views/WebClient | 2 +- 8 files changed, 52 insertions(+), 40 deletions(-) diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 9974dab8..9a54b7af 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -261,7 +261,10 @@ namespace Kyoo.Controllers return obj; } - private async Task SetRelation(T1 obj, Task> loader, Action> setter, Action inverse) + private async Task SetRelation(T1 obj, + Task> loader, + Action> setter, + Action inverse) { ICollection loaded = await loader; setter(obj, loaded); @@ -300,8 +303,8 @@ namespace Kyoo.Controllers (Show s, nameof(Show.ExternalIDs)) => SetRelation(s, ProviderRepository.GetMetadataID(x => x.ShowID == obj.ID), - (x, y) => x.ExternalIDs = y, - (x, y) => x.Show = y), + (x, y) => x.ExternalIDs = y, + (x, y) => { x.Show = y; x.ShowID = y.ID; }), (Show s, nameof(Show.Genres)) => GenreRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs index 4264a469..6fe964d4 100644 --- a/Kyoo.Common/Models/LibraryItem.cs +++ b/Kyoo.Common/Models/LibraryItem.cs @@ -1,5 +1,6 @@ using System; using System.Linq.Expressions; +using Kyoo.Models.Attributes; namespace Kyoo.Models { @@ -20,7 +21,8 @@ namespace Kyoo.Models public string TrailerUrl { get; set; } public int? StartYear { get; set; } public int? EndYear { get; set; } - public string Poster { get; set; } + [SerializeAs("{HOST}/api/{_type}/{Slug}/poster")] public string Poster { get; set; } + private string _type => Type == ItemType.Collection ? "collection" : "show"; public ItemType Type { get; set; } public LibraryItem() {} diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index ca1aec5d..2743cf3c 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -9,7 +9,7 @@ namespace Kyoo.Models public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } - public string Poster { get; set; } + [SerializeAs("{HOST}/api/people/{Slug}/poster")] public string Poster { get; set; } [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } [EditableRelation] [LoadableRelation] public virtual ICollection Roles { get; set; } diff --git a/Kyoo.Common/Models/WatchItem.cs b/Kyoo.Common/Models/WatchItem.cs index f552efce..fc260699 100644 --- a/Kyoo.Common/Models/WatchItem.cs +++ b/Kyoo.Common/Models/WatchItem.cs @@ -25,31 +25,34 @@ namespace Kyoo.Models public class WatchItem { - public readonly int EpisodeID; + public int EpisodeID { get; set; } - public string ShowTitle; - public string ShowSlug; - public int SeasonNumber; - public int EpisodeNumber; - public string Title; - public string Slug; - public DateTime? ReleaseDate; - [SerializeIgnore] public string Path; - public Episode PreviousEpisode; - public Episode NextEpisode; - public bool IsMovie; + public string ShowTitle { get; set; } + public string ShowSlug { get; set; } + public int SeasonNumber { get; set; } + public int EpisodeNumber { get; set; } + public string Title { get; set; } + public string Slug { get; set; } + public DateTime? ReleaseDate { get; set; } + [SerializeIgnore] public string Path { get; set; } + public Episode PreviousEpisode { get; set; } + public Episode NextEpisode { get; set; } + public bool IsMovie { get; set; } - public string Container; - public Track Video; - public ICollection Audios; - public ICollection Subtitles; - public ICollection Chapters; + [SerializeAs("{HOST}/api/show/{ShowSlug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/show/{ShowSlug}/logo")] public string Logo { get; set; } + [SerializeAs("{HOST}/api/show/{ShowSlug}/backdrop")] public string Backdrop { get; set; } + + public string Container { get; set; } + public Track Video { get; set; } + public ICollection Audios { get; set; } + public ICollection Subtitles { get; set; } + public ICollection Chapters { get; set; } public WatchItem() { } private WatchItem(int episodeID, - string showTitle, - string showSlug, + Show show, int seasonNumber, int episodeNumber, string title, @@ -57,21 +60,25 @@ namespace Kyoo.Models string path) { EpisodeID = episodeID; - ShowTitle = showTitle; - ShowSlug = showSlug; + ShowTitle = show.Title; + ShowSlug = show.Slug; SeasonNumber = seasonNumber; EpisodeNumber = episodeNumber; Title = title; ReleaseDate = releaseDate; Path = path; + IsMovie = show.IsMovie; + + Poster = show.Poster; + Logo = show.Logo; + Backdrop = show.Backdrop; Container = Path.Substring(Path.LastIndexOf('.') + 1); Slug = Episode.GetSlug(ShowSlug, seasonNumber, episodeNumber); } private WatchItem(int episodeID, - string showTitle, - string showSlug, + Show show, int seasonNumber, int episodeNumber, string title, @@ -80,7 +87,7 @@ namespace Kyoo.Models Track video, ICollection audios, ICollection subtitles) - : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path) + : this(episodeID, show, seasonNumber, episodeNumber, title, releaseDate, path) { Video = video; Audios = audios; @@ -89,11 +96,13 @@ namespace Kyoo.Models public static async Task FromEpisode(Episode ep, ILibraryManager library) { - Show show = await library.GetShow(ep.ShowID); Episode previous = null; Episode next = null; - if (!show.IsMovie) + await library.Load(ep, x => x.Show); + await library.Load(ep, x => x.Tracks); + + if (!ep.Show.IsMovie) { if (ep.EpisodeNumber > 1) previous = await library.GetEpisode(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber - 1); @@ -109,11 +118,9 @@ namespace Kyoo.Models else next = await library.GetEpisode(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber + 1); } - - await library.Load(ep, x => x.Tracks); + return new WatchItem(ep.ID, - show.Title, - show.Slug, + ep.Show, ep.SeasonNumber, ep.EpisodeNumber, ep.Title, @@ -123,7 +130,6 @@ namespace Kyoo.Models ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(), ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray()) { - IsMovie = show.IsMovie, PreviousEpisode = previous, NextEpisode = next, Chapters = await GetChapters(ep.Path) diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 8702e66b..ce19ba29 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -119,7 +119,8 @@ namespace Kyoo.Controllers if (value == "HOST") return _host; - PropertyInfo properties = target.GetType().GetProperties() + PropertyInfo properties = target.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .FirstOrDefault(y => y.Name == value); if (properties == null) return null; diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 72f4d354..2e2e7c2c 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -51,7 +51,7 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - return ApplyFilters(_database.MetadataIds, + return ApplyFilters(_database.MetadataIds.Include(y => y.Provider), x => _database.MetadataIds.FirstOrDefaultAsync(y => y.ID == x), x => x.ID, where, diff --git a/Kyoo/Views/API/PeopleApi.cs b/Kyoo/Views/API/PeopleApi.cs index 88088757..602c022c 100644 --- a/Kyoo/Views/API/PeopleApi.cs +++ b/Kyoo/Views/API/PeopleApi.cs @@ -23,7 +23,7 @@ namespace Kyoo.Api : base(libraryManager.PeopleRepository, configuration) { _libraryManager = libraryManager; - _peoplePath = configuration.GetValue("peoplePath"); + _peoplePath = Path.GetFullPath(configuration.GetValue("peoplePath")); } [HttpGet("{id:int}/role")] diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index 35e6a601..f36ac1bb 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit 35e6a601edc494d1bea142fd419c7750c18d21d9 +Subproject commit f36ac1bb9bf60329a7f04ba76730f43ded7b0d9d From fdf2f37cebd1d3e338894f4b17366ccd50c21e63 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 15 Mar 2021 21:06:11 +0100 Subject: [PATCH 32/54] Adding handling of provider's logo locally --- Kyoo.Common/Controllers/IThumbnailsManager.cs | 3 +- Kyoo.Common/Models/Resources/Collection.cs | 2 +- Kyoo.Common/Models/Resources/ProviderID.cs | 2 +- Kyoo/Controllers/TaskManager.cs | 2 +- Kyoo/Controllers/ThumbnailsManager.cs | 43 ++++++++++++------- Kyoo/Startup.cs | 19 +++++++- Kyoo/Tasks/Crawler.cs | 1 - Kyoo/Tasks/ExtractMetadata.cs | 2 + Kyoo/Tasks/MetadataProviderLoader.cs | 6 ++- Kyoo/Views/API/ProviderApi.cs | 23 ++++++++++ Kyoo/appsettings.json | 1 + 11 files changed, 81 insertions(+), 23 deletions(-) diff --git a/Kyoo.Common/Controllers/IThumbnailsManager.cs b/Kyoo.Common/Controllers/IThumbnailsManager.cs index 0e966b12..32666e84 100644 --- a/Kyoo.Common/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Common/Controllers/IThumbnailsManager.cs @@ -9,6 +9,7 @@ namespace Kyoo.Controllers Task Validate(Show show, bool alwaysDownload = false); Task Validate(Season season, bool alwaysDownload = false); Task Validate(Episode episode, bool alwaysDownload = false); - Task> Validate(IEnumerable actors, bool alwaysDownload = false); + Task Validate(People actors, bool alwaysDownload = false); + Task Validate(ProviderID actors, bool alwaysDownload = false); } } diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index b1274278..3c7bed25 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -8,7 +8,7 @@ namespace Kyoo.Models public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } - [SerializeAs("{HOST}/api/library/{Slug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/collection/{Slug}/poster")] public string Poster { get; set; } public string Overview { get; set; } [LoadableRelation] public virtual ICollection Shows { get; set; } [LoadableRelation] public virtual ICollection Libraries { get; set; } diff --git a/Kyoo.Common/Models/Resources/ProviderID.cs b/Kyoo.Common/Models/Resources/ProviderID.cs index a874d09e..85374979 100644 --- a/Kyoo.Common/Models/Resources/ProviderID.cs +++ b/Kyoo.Common/Models/Resources/ProviderID.cs @@ -8,7 +8,7 @@ namespace Kyoo.Models public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } - public string Logo { get; set; } + [SerializeAs("{HOST}/api/providers/{Slug}/logo")] public string Logo { get; set; } [LoadableRelation] public virtual ICollection Libraries { get; set; } diff --git a/Kyoo/Controllers/TaskManager.cs b/Kyoo/Controllers/TaskManager.cs index 9a791fdc..519fb43f 100644 --- a/Kyoo/Controllers/TaskManager.cs +++ b/Kyoo/Controllers/TaskManager.cs @@ -110,7 +110,7 @@ namespace Kyoo.Controllers _tasks.AddRange(CoreTaskHolder.Tasks.Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug)))); IEnumerable prerunTasks = _tasks.Select(x => x.task) - .Where(x => x.RunOnStartup && x.Priority == Int32.MaxValue); + .Where(x => x.RunOnStartup && x.Priority == int.MaxValue); foreach (ITask task in prerunTasks) task.Run(_serviceProvider, _taskToken.Token); diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index ac4aeeec..783e7a0a 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -1,10 +1,10 @@ using Kyoo.Models; using Microsoft.Extensions.Configuration; using System; -using System.Collections.Generic; using System.IO; using System.Net; using System.Threading.Tasks; +using JetBrains.Annotations; namespace Kyoo.Controllers { @@ -26,7 +26,7 @@ namespace Kyoo.Controllers } catch (WebException exception) { - await Console.Error.WriteLineAsync($"{what} could not be downloaded.\n\tError: {exception.Message}."); + await Console.Error.WriteLineAsync($"{what} could not be downloaded. Error: {exception.Message}."); } } @@ -53,26 +53,23 @@ namespace Kyoo.Controllers if (alwaysDownload || !File.Exists(backdropPath)) await DownloadImage(show.Backdrop, backdropPath, $"The backdrop of {show.Title}"); } + + foreach (PeopleRole role in show.People) + await Validate(role.People, alwaysDownload); return show; } - public async Task> Validate(IEnumerable people, bool alwaysDownload) + public async Task Validate([NotNull] People people, bool alwaysDownload) { if (people == null) - return null; - + throw new ArgumentNullException(nameof(people)); string root = _config.GetValue("peoplePath"); - Directory.CreateDirectory(root); + string localPath = Path.Combine(root, people.Slug + ".jpg"); - foreach (PeopleRole peop in people) - { - string localPath = Path.Combine(root, peop.People.Slug + ".jpg"); - if (peop.People.Poster == null) - continue; - if (alwaysDownload || !File.Exists(localPath)) - await DownloadImage(peop.People.Poster, localPath, $"The profile picture of {peop.People.Name}"); - } + Directory.CreateDirectory(root); + if (alwaysDownload || !File.Exists(localPath)) + await DownloadImage(people.Poster, localPath, $"The profile picture of {people.Name}"); return people; } @@ -100,9 +97,25 @@ namespace Kyoo.Controllers { string localPath = Path.ChangeExtension(episode.Path, "jpg"); if (alwaysDownload || !File.Exists(localPath)) - await DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Show.Title}"); + await DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}"); } return episode; } + + public async Task Validate(ProviderID provider, bool alwaysDownload) + { + if (provider.Logo == null) + return provider; + + string root = _config.GetValue("peoplePath"); + string localPath = Path.Combine(root, provider.Slug + ".jpg"); + + Directory.CreateDirectory(root); + if (alwaysDownload || !File.Exists(localPath)) + await DownloadImage(provider.Logo, localPath, $"The thumbnail of {provider.Slug}"); + return provider; + } + + //TODO add get thumbs here } } diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 72666c57..7cede9d7 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -43,6 +43,10 @@ namespace Kyoo { configuration.RootPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"); }); + services.AddResponseCompression(x => + { + x.EnableForHttps = true; + }); services.AddControllers() .AddNewtonsoftJson(x => @@ -187,7 +191,20 @@ namespace Kyoo app.UseRouting(); - app.UseCookiePolicy(new CookiePolicyOptions + // app.Use((ctx, next) => + // { + // ctx.Response.Headers.Remove("X-Powered-By"); + // ctx.Response.Headers.Remove("Server"); + // ctx.Response.Headers.Add("Feature-Policy", "autoplay 'self'; fullscreen"); + // ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"); + // ctx.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + // ctx.Response.Headers.Add("Referrer-Policy", "no-referrer"); + // ctx.Response.Headers.Add("Access-Control-Allow-Origin", "null"); + // ctx.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + // return next(); + // }); + app.UseResponseCompression(); + app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict }); diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 0cbf431a..0c0db5c4 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -291,7 +291,6 @@ namespace Kyoo.Controllers show.Slug += $"-{show.StartYear}"; await libraryManager.RegisterShow(show); } - await _thumbnailsManager.Validate(show.People); await _thumbnailsManager.Validate(show); return show; } diff --git a/Kyoo/Tasks/ExtractMetadata.cs b/Kyoo/Tasks/ExtractMetadata.cs index 85c81612..f78caddf 100644 --- a/Kyoo/Tasks/ExtractMetadata.cs +++ b/Kyoo/Tasks/ExtractMetadata.cs @@ -72,6 +72,7 @@ namespace Kyoo.Tasks { if (thumbs) await _thumbnails!.Validate(show, true); + await _library.Load(show, x => x.Seasons); foreach (Season season in show.Seasons) { if (token.IsCancellationRequested) @@ -84,6 +85,7 @@ namespace Kyoo.Tasks { if (thumbs) await _thumbnails!.Validate(season, true); + await _library.Load(season, x => x.Episodes); foreach (Episode episode in season.Episodes) { if (token.IsCancellationRequested) diff --git a/Kyoo/Tasks/MetadataProviderLoader.cs b/Kyoo/Tasks/MetadataProviderLoader.cs index 95539677..5811775e 100644 --- a/Kyoo/Tasks/MetadataProviderLoader.cs +++ b/Kyoo/Tasks/MetadataProviderLoader.cs @@ -21,13 +21,15 @@ namespace Kyoo.Tasks { using IServiceScope serviceScope = serviceProvider.CreateScope(); IProviderRepository providers = serviceScope.ServiceProvider.GetService(); + IThumbnailsManager thumbnails = serviceScope.ServiceProvider.GetService(); IPluginManager pluginManager = serviceScope.ServiceProvider.GetService(); - foreach (IMetadataProvider provider in pluginManager.GetPlugins()) + foreach (IMetadataProvider provider in pluginManager!.GetPlugins()) { if (string.IsNullOrEmpty(provider.Provider.Slug)) throw new ArgumentException($"Empty provider slug (name: {provider.Provider.Name})."); - await providers.CreateIfNotExists(provider.Provider); + await providers!.CreateIfNotExists(provider.Provider); + await thumbnails!.Validate(provider.Provider); } } diff --git a/Kyoo/Views/API/ProviderApi.cs b/Kyoo/Views/API/ProviderApi.cs index c2798434..6d1cbb86 100644 --- a/Kyoo/Views/API/ProviderApi.cs +++ b/Kyoo/Views/API/ProviderApi.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; @@ -14,11 +16,32 @@ namespace Kyoo.Api public class ProviderAPI : CrudApi { private readonly ILibraryManager _libraryManager; + private readonly string _providerPath; public ProviderAPI(ILibraryManager libraryManager, IConfiguration config) : base(libraryManager.ProviderRepository, config) { _libraryManager = libraryManager; + _providerPath = Path.GetFullPath(config.GetValue("providerPath")); + } + + [HttpGet("{id:int}/logo")] + [Authorize(Policy="Read")] + public async Task GetLogo(int id) + { + string slug = (await _libraryManager.GetPeople(id)).Slug; + return GetLogo(slug); + } + + [HttpGet("{slug}/logo")] + [Authorize(Policy="Read")] + public IActionResult GetLogo(string slug) + { + string thumbPath = Path.GetFullPath(Path.Combine(_providerPath, slug + ".jpg")); + if (!thumbPath.StartsWith(_providerPath) || !System.IO.File.Exists(thumbPath)) + return NotFound(); + + return new PhysicalFileResult(Path.GetFullPath(thumbPath), "image/jpg"); } } } \ No newline at end of file diff --git a/Kyoo/appsettings.json b/Kyoo/appsettings.json index 19f3d82f..3cc8f735 100644 --- a/Kyoo/appsettings.json +++ b/Kyoo/appsettings.json @@ -29,6 +29,7 @@ "transmuxTempPath": "cached/kyoo/transmux", "transcodeTempPath": "cached/kyoo/transcode", "peoplePath": "people", + "providerPath": "providers", "profilePicturePath": "users/", "plugins": "plugins/", "defaultPermissions": "read,play,write,admin", From d38926924cd0dcb428f3477921bfb0e92ba14e18 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 15 Mar 2021 22:02:01 +0100 Subject: [PATCH 33/54] Fixing providers's thumbnails and adding security headers --- Kyoo/Controllers/ThumbnailsManager.cs | 4 ++-- Kyoo/Startup.cs | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 783e7a0a..a2f0f7ac 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -107,12 +107,12 @@ namespace Kyoo.Controllers if (provider.Logo == null) return provider; - string root = _config.GetValue("peoplePath"); + string root = _config.GetValue("providerPath"); string localPath = Path.Combine(root, provider.Slug + ".jpg"); Directory.CreateDirectory(root); if (alwaysDownload || !File.Exists(localPath)) - await DownloadImage(provider.Logo, localPath, $"The thumbnail of {provider.Slug}"); + await DownloadImage(provider.Logo, localPath, $"The logo of {provider.Slug}"); return provider; } diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 7cede9d7..ae5ec44b 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -191,18 +191,18 @@ namespace Kyoo app.UseRouting(); - // app.Use((ctx, next) => - // { - // ctx.Response.Headers.Remove("X-Powered-By"); - // ctx.Response.Headers.Remove("Server"); - // ctx.Response.Headers.Add("Feature-Policy", "autoplay 'self'; fullscreen"); - // ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"); - // ctx.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); - // ctx.Response.Headers.Add("Referrer-Policy", "no-referrer"); - // ctx.Response.Headers.Add("Access-Control-Allow-Origin", "null"); - // ctx.Response.Headers.Add("X-Content-Type-Options", "nosniff"); - // return next(); - // }); + app.Use((ctx, next) => + { + ctx.Response.Headers.Remove("X-Powered-By"); + ctx.Response.Headers.Remove("Server"); + ctx.Response.Headers.Add("Feature-Policy", "autoplay 'self'; fullscreen"); + ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"); + ctx.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + ctx.Response.Headers.Add("Referrer-Policy", "no-referrer"); + ctx.Response.Headers.Add("Access-Control-Allow-Origin", "null"); + ctx.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + return next(); + }); app.UseResponseCompression(); app.UseCookiePolicy(new CookiePolicyOptions { From d2a38c9b3d29722fda9a28f6acc19c229a1f62ee Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 15 Mar 2021 23:52:11 +0100 Subject: [PATCH 34/54] Fixing tracks editing --- Kyoo.Common/Models/Resources/Episode.cs | 4 ++-- Kyoo.Common/Utility.cs | 18 +++++++++++++++++- Kyoo.CommonAPI/LocalRepository.cs | 11 ++++++++--- .../Repositories/EpisodeRepository.cs | 17 +++++++++++++++-- .../Repositories/TrackRepository.cs | 9 ++++++++- Kyoo/Models/DatabaseContext.cs | 7 ++++++- Kyoo/Startup.cs | 6 +++--- 7 files changed, 59 insertions(+), 13 deletions(-) diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 0515da46..c346be0e 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -27,9 +27,9 @@ namespace Kyoo.Models public int Runtime { get; set; } //This runtime variable should be in minutes - [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [LoadableRelation] public virtual ICollection Tracks { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection Tracks { get; set; } public Episode() { } diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 521abc11..75e2ce3b 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -143,7 +143,7 @@ namespace Kyoo ? Activator.CreateInstance(property.PropertyType) : null; - if (value?.Equals(defaultValue) == false) + if (value?.Equals(defaultValue) == false && value != property.GetValue(first)) property.SetValue(first, value); } @@ -303,6 +303,22 @@ namespace Kyoo foreach (T i in self) action(i); } + + public static async Task ForEachAsync([CanBeNull] this IEnumerable self, Func action) + { + if (self == null) + return; + foreach (T i in self) + await action(i); + } + + public static async Task ForEachAsync([CanBeNull] this IAsyncEnumerable self, Action action) + { + if (self == null) + return; + await foreach (T i in self) + action(i); + } private static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args) { diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index d5c7f790..cf6ed4e3 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -43,6 +43,11 @@ namespace Kyoo.Controllers { return Database.Set().FirstOrDefaultAsync(x => x.ID == id); } + + public virtual Task GetWithTracking(int id) + { + return Database.Set().AsTracking().FirstOrDefaultAsync(x => x.ID == id); + } public virtual Task Get(string slug) { @@ -159,7 +164,7 @@ namespace Kyoo.Controllers Database.ChangeTracker.LazyLoadingEnabled = false; try { - T old = await Get(edited.ID); + T old = await GetWithTracking(edited.ID); if (old == null) throw new ItemNotFound($"No resource found with the ID {edited.ID}."); @@ -180,8 +185,8 @@ namespace Kyoo.Controllers await navigation.LoadAsync(); // TODO this may be usless for lists since the API does not return IDs but the // TODO LinkEquality does not check slugs (their are lazy loaded and only the ID is available) - if (Utility.ResourceEquals(getter.GetClrValue(edited), getter.GetClrValue(old))) - navigation.Metadata.PropertyInfo.SetValue(edited, default); + // if (Utility.ResourceEquals(getter.GetClrValue(edited), getter.GetClrValue(old))) + // navigation.Metadata.PropertyInfo.SetValue(edited, default); } else navigation.Metadata.PropertyInfo.SetValue(edited, default); diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 222e18de..7efadd95 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -15,15 +15,20 @@ namespace Kyoo.Controllers private readonly DatabaseContext _database; private readonly IProviderRepository _providers; private readonly IShowRepository _shows; + private readonly ITrackRepository _tracks; protected override Expression> DefaultSort => x => x.EpisodeNumber; - public EpisodeRepository(DatabaseContext database, IProviderRepository providers, IShowRepository shows) + public EpisodeRepository(DatabaseContext database, + IProviderRepository providers, + IShowRepository shows, + ITrackRepository tracks) : base(database) { _database = database; _providers = providers; _shows = shows; + _tracks = tracks; } @@ -161,6 +166,14 @@ namespace Kyoo.Controllers await base.Validate(resource); + if (resource.Tracks != null) + { + // TODO remove old values + resource.Tracks = await resource.Tracks + .SelectAsync(x => _tracks.CreateIfNotExists(x, true)) + .ToListAsync(); + } + if (resource.ExternalIDs != null) { foreach (MetadataID link in resource.ExternalIDs) @@ -181,10 +194,10 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; + await obj.Tracks.ForEachAsync(x => _tracks.CreateIfNotExists(x, true)); if (obj.ExternalIDs != null) foreach (MetadataID entry in obj.ExternalIDs) _database.Entry(entry).State = EntityState.Deleted; - // Since Tracks & Episodes are on the same database and handled by dotnet-ef, we can't use the repository to delete them. await _database.SaveChangesAsync(); } } diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 413b202f..26b47d47 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -36,7 +36,12 @@ namespace Kyoo.Controllers await _database.DisposeAsync(); } - public Task Get(string slug, StreamType type = StreamType.Unknown) + public override Task Get(string slug) + { + return Get(slug, StreamType.Unknown); + } + + public Task Get(string slug, StreamType type) { Match match = Regex.Match(slug, @"(?.*)-s(?\d+)e(?\d+)\.(?.{0,3})(?-forced)?(\..*)?"); @@ -93,6 +98,8 @@ namespace Kyoo.Controllers return obj; } + + protected override Task Validate(Track resource) { return Task.CompletedTask; diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 250c381b..6122d40a 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -12,7 +12,10 @@ namespace Kyoo { public class DatabaseContext : DbContext { - public DatabaseContext(DbContextOptions options) : base(options) { } + public DatabaseContext(DbContextOptions options) : base(options) + { + ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + } public DbSet Libraries { get; set; } public DbSet Collections { get; set; } @@ -35,6 +38,8 @@ namespace Kyoo NpgsqlConnection.GlobalTypeMapper.MapEnum(); NpgsqlConnection.GlobalTypeMapper.MapEnum(); NpgsqlConnection.GlobalTypeMapper.MapEnum(); + + ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index ae5ec44b..52a628fe 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -58,9 +58,9 @@ namespace Kyoo services.AddDbContext(options => { - options.UseNpgsql(_configuration.GetConnectionString("Database")); - // .EnableSensitiveDataLogging() - // .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); + options.UseNpgsql(_configuration.GetConnectionString("Database")) + .EnableSensitiveDataLogging() + .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); }, ServiceLifetime.Transient); services.AddDbContext(options => From b84baae99a1ef63274fa032c6716f144dec8ac65 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Mar 2021 00:24:13 +0100 Subject: [PATCH 35/54] Fixing subtitle re-extraction --- Kyoo.Common/Utility.cs | 16 +++++++++++++ Kyoo.CommonAPI/LocalRepository.cs | 23 +++++++++++++++++-- .../Repositories/EpisodeRepository.cs | 15 ++++++------ Kyoo/Tasks/ExtractMetadata.cs | 6 +++-- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 75e2ce3b..1504abf5 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -304,6 +304,14 @@ namespace Kyoo action(i); } + public static void ForEach([CanBeNull] this IEnumerable self, Action action) + { + if (self == null) + return; + foreach (object i in self) + action(i); + } + public static async Task ForEachAsync([CanBeNull] this IEnumerable self, Func action) { if (self == null) @@ -319,6 +327,14 @@ namespace Kyoo await foreach (T i in self) action(i); } + + public static async Task ForEachAsync([CanBeNull] this IEnumerable self, Func action) + { + if (self == null) + return; + foreach (object i in self) + await action(i); + } private static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args) { diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index cf6ed4e3..0b6f2572 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; -using JetBrains.Annotations; using Kyoo.CommonApi; using Kyoo.Models; using Kyoo.Models.Attributes; @@ -119,7 +118,7 @@ namespace Kyoo.Controllers return query.CountAsync(); } - public virtual async Task Create([NotNull] T obj) + public virtual async Task Create(T obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -169,6 +168,26 @@ namespace Kyoo.Controllers if (old == null) throw new ItemNotFound($"No resource found with the ID {edited.ID}."); + // foreach (PropertyInfo navigation in typeof(T).GetProperties() + // .Where(x => x.GetCustomAttribute() != null)) + // { + // if (navigation.GetCustomAttribute() == null) + // { + // navigation.SetValue(edited, default); + // continue; + // } + // + // if (resetOld || navigation.GetValue(edited) != default) + // { + // // TODO only works for X To One and not X to Many + // await _library.Value.Load(old, navigation.Name); + // object value = navigation.GetValue(old); + // if (value is IEnumerable list) // TODO handle externalIds & PeopleRoles + // list.ForEach(x => _library.Value.Delete(x)); + // else if (value is IResource resource) + // _library.Value.Delete(resource); + // } + // } foreach (NavigationEntry navigation in Database.Entry(old).Navigations) { if (navigation.Metadata.PropertyInfo.GetCustomAttribute() != null) diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 7efadd95..ee53f331 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -166,13 +166,12 @@ namespace Kyoo.Controllers await base.Validate(resource); - if (resource.Tracks != null) - { - // TODO remove old values - resource.Tracks = await resource.Tracks - .SelectAsync(x => _tracks.CreateIfNotExists(x, true)) - .ToListAsync(); - } + // if (resource.Tracks != null) + // { + // resource.Tracks = await resource.Tracks + // .SelectAsync(x => _tracks.CreateIfNotExists(x, true)) + // .ToListAsync(); + // } if (resource.ExternalIDs != null) { @@ -194,7 +193,7 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - await obj.Tracks.ForEachAsync(x => _tracks.CreateIfNotExists(x, true)); + // await obj.Tracks.ForEachAsync(x => _tracks.CreateIfNotExists(x, true)); if (obj.ExternalIDs != null) foreach (MetadataID entry in obj.ExternalIDs) _database.Entry(entry).State = EntityState.Deleted; diff --git a/Kyoo/Tasks/ExtractMetadata.cs b/Kyoo/Tasks/ExtractMetadata.cs index f78caddf..4dccc70f 100644 --- a/Kyoo/Tasks/ExtractMetadata.cs +++ b/Kyoo/Tasks/ExtractMetadata.cs @@ -100,9 +100,11 @@ namespace Kyoo.Tasks await _thumbnails!.Validate(episode, true); if (subs) { - // TODO handle external subtites. + await _library.Load(episode, x => x.Tracks); episode.Tracks = (await _transcoder!.ExtractInfos(episode.Path)) - .Where(x => x.Type != StreamType.Font).ToArray(); + .Where(x => x.Type != StreamType.Font) + .Concat(episode.Tracks.Where(x => x.IsExternal)) + .ToList(); await _library.EditEpisode(episode, false); } } From 27c89ccc85988c4a24518c3d911dec26a9a22422 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Mar 2021 01:41:26 +0100 Subject: [PATCH 36/54] Trying things with a generic edit function with repositories --- .../Models/Attributes/RelationAttributes.cs | 11 ++ Kyoo.Common/Models/Resources/Show.cs | 3 +- Kyoo.CommonAPI/JsonSerializer.cs | 2 +- Kyoo.CommonAPI/LocalRepository.cs | 108 ++++++++++++------ Kyoo/Models/DatabaseContext.cs | 2 + 5 files changed, 86 insertions(+), 40 deletions(-) diff --git a/Kyoo.Common/Models/Attributes/RelationAttributes.cs b/Kyoo.Common/Models/Attributes/RelationAttributes.cs index aac0e633..fedfc6db 100644 --- a/Kyoo.Common/Models/Attributes/RelationAttributes.cs +++ b/Kyoo.Common/Models/Attributes/RelationAttributes.cs @@ -17,4 +17,15 @@ namespace Kyoo.Models.Attributes RelationID = relationID; } } + + [AttributeUsage(AttributeTargets.Property)] + public class LinkRelationAttribute : Attribute + { + public string Relation { get; } + + public LinkRelationAttribute(string relation) + { + Relation = relation; + } + } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 7b948b55..f77bfc2e 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -37,7 +37,8 @@ namespace Kyoo.Models [LoadableRelation] public virtual ICollection Collections { get; set; } #if ENABLE_INTERNAL_LINKS - [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } + [LinkRelation(nameof(Libraries))] [SerializeIgnore] + public virtual ICollection> LibraryLinks { get; set; } [SerializeIgnore] public virtual ICollection> CollectionLinks { get; set; } [SerializeIgnore] public virtual ICollection> GenreLinks { get; set; } #endif diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index ce19ba29..c9238049 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -132,7 +132,7 @@ namespace Kyoo.Controllers public void SetValue(object target, object value) { - throw new NotImplementedException(); + // Values are ignored and should not be editable, except if the internal value is set. } } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 0b6f2572..489f9742 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -153,6 +154,7 @@ namespace Kyoo.Controllers throw; } } + public virtual async Task Edit(T edited, bool resetOld) { @@ -168,47 +170,77 @@ namespace Kyoo.Controllers if (old == null) throw new ItemNotFound($"No resource found with the ID {edited.ID}."); - // foreach (PropertyInfo navigation in typeof(T).GetProperties() - // .Where(x => x.GetCustomAttribute() != null)) - // { - // if (navigation.GetCustomAttribute() == null) - // { - // navigation.SetValue(edited, default); - // continue; - // } - // - // if (resetOld || navigation.GetValue(edited) != default) - // { - // // TODO only works for X To One and not X to Many - // await _library.Value.Load(old, navigation.Name); - // object value = navigation.GetValue(old); - // if (value is IEnumerable list) // TODO handle externalIds & PeopleRoles - // list.ForEach(x => _library.Value.Delete(x)); - // else if (value is IResource resource) - // _library.Value.Delete(resource); - // } - // } - foreach (NavigationEntry navigation in Database.Entry(old).Navigations) + List navigations = typeof(T).GetProperties() + .Where(x => x.GetCustomAttribute() != null) + .ToList(); + List links = typeof(T).GetProperties() + .Select(x => x.GetCustomAttribute()?.Relation) + .Where(x => x != null) + .ToList(); + + // This handle every links so every Many to Many + // TODO should handle non links value (One to Many) + foreach (string relationName in links) { - if (navigation.Metadata.PropertyInfo.GetCustomAttribute() != null) - { - if (resetOld) - { - await navigation.LoadAsync(); - continue; - } - IClrPropertyGetter getter = navigation.Metadata.GetGetter(); + PropertyInfo relation = navigations.Find(x => x.Name == relationName); + navigations.Remove(relation); - if (getter.HasDefaultValue(edited)) - continue; - await navigation.LoadAsync(); - // TODO this may be usless for lists since the API does not return IDs but the - // TODO LinkEquality does not check slugs (their are lazy loaded and only the ID is available) - // if (Utility.ResourceEquals(getter.GetClrValue(edited), getter.GetClrValue(old))) - // navigation.Metadata.PropertyInfo.SetValue(edited, default); + if (relation!.GetValue(edited) == null && !resetOld) + continue; + + await _library.Load(old, relation.Name); + IEnumerable oValues = (relation.GetValue(old) as IEnumerable)!.Cast(); + List nValues = (relation.GetValue(edited) as IEnumerable)?.Cast().ToList(); + + foreach (IResource x in oValues) + { + int nIndex = nValues?.FindIndex(y => Utility.ResourceEquals(x, y)) ?? -1; + if (nIndex == -1 || resetOld) + await _linkManager.Delete(old, x); + else + nValues!.RemoveAt(nIndex); + } + + await nValues.ForEachAsync(x => _linkManager.Add(old, x)); + } + + // This handle every X to One + foreach (PropertyInfo relation in navigations) + { + IResource oValues = relation.GetValue(old) as IResource; + IResource nValues = relation.GetValue(edited) as IResource; + + if (nValues == null && !resetOld) + continue; + + if (false) // TODO change if false with if relation is casquade delete + await _library.Delete(oValue); + relation.SetValue(old, nValues); + // TODO call create if not exist + // TODO change relationID to the new value ID. + } + + + // This handle every One to Many or Many to One + foreach (PropertyInfo navigation in navigations) + { + if (resetOld || navigation.GetValue(edited) != default) + { + // TODO only works for X To One and not X to Many + await _library.Value.Load(old, navigation.Name); + object value = navigation.GetValue(old); + if (value is IEnumerable list) // TODO handle externalIds & PeopleRoles (implement Link<> for those) + list.ForEach(x => _library.Value.Delete(x)); + else if (value is IResource resource) + _library.Value.Delete(resource); + } + + + if (navigation.GetCustomAttribute() == null) + { + navigation.SetValue(edited, default); + continue; } - else - navigation.Metadata.PropertyInfo.SetValue(edited, default); } if (resetOld) diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 6122d40a..125f0ed8 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -15,6 +15,7 @@ namespace Kyoo public DatabaseContext(DbContextOptions options) : base(options) { ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + ChangeTracker.LazyLoadingEnabled = false; } public DbSet Libraries { get; set; } @@ -40,6 +41,7 @@ namespace Kyoo NpgsqlConnection.GlobalTypeMapper.MapEnum(); ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + ChangeTracker.LazyLoadingEnabled = false; } protected override void OnModelCreating(ModelBuilder modelBuilder) From 2317445053ad813c2b128ebe19684047fae6c971 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Mar 2021 02:33:31 +0100 Subject: [PATCH 37/54] Starting edits in a simple way --- .../Models/Attributes/RelationAttributes.cs | 11 --- Kyoo.Common/Models/Resources/Show.cs | 3 +- Kyoo.Common/Utility.cs | 9 +- Kyoo.CommonAPI/LocalRepository.cs | 94 ++----------------- .../Repositories/EpisodeRepository.cs | 27 ++++-- 5 files changed, 33 insertions(+), 111 deletions(-) diff --git a/Kyoo.Common/Models/Attributes/RelationAttributes.cs b/Kyoo.Common/Models/Attributes/RelationAttributes.cs index fedfc6db..aac0e633 100644 --- a/Kyoo.Common/Models/Attributes/RelationAttributes.cs +++ b/Kyoo.Common/Models/Attributes/RelationAttributes.cs @@ -17,15 +17,4 @@ namespace Kyoo.Models.Attributes RelationID = relationID; } } - - [AttributeUsage(AttributeTargets.Property)] - public class LinkRelationAttribute : Attribute - { - public string Relation { get; } - - public LinkRelationAttribute(string relation) - { - Relation = relation; - } - } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index f77bfc2e..7b948b55 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -37,8 +37,7 @@ namespace Kyoo.Models [LoadableRelation] public virtual ICollection Collections { get; set; } #if ENABLE_INTERNAL_LINKS - [LinkRelation(nameof(Libraries))] [SerializeIgnore] - public virtual ICollection> LibraryLinks { get; set; } + [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } [SerializeIgnore] public virtual ICollection> CollectionLinks { get; set; } [SerializeIgnore] public virtual ICollection> GenreLinks { get; set; } #endif diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 1504abf5..d7be7dec 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -124,7 +124,7 @@ namespace Kyoo return first; } - public static T Complete(T first, T second) + public static T Complete(T first, T second, Func ignore = null) { if (first == null) throw new ArgumentNullException(nameof(first)); @@ -135,6 +135,9 @@ namespace Kyoo IEnumerable properties = type.GetProperties() .Where(x => x.CanRead && x.CanWrite && Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null); + + if (ignore != null) + properties = properties.Where(ignore); foreach (PropertyInfo property in properties) { @@ -253,11 +256,11 @@ namespace Kyoo return types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); } - public static async IAsyncEnumerable SelectAsync([NotNull] this IEnumerable self, + public static async IAsyncEnumerable SelectAsync([CanBeNull] this IEnumerable self, [NotNull] Func> mapper) { if (self == null) - throw new ArgumentNullException(nameof(self)); + yield break; if (mapper == null) throw new ArgumentNullException(nameof(mapper)); diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 489f9742..b35bb041 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -10,8 +9,6 @@ using Kyoo.Models; using Kyoo.Models.Attributes; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Metadata; namespace Kyoo.Controllers { @@ -155,7 +152,6 @@ namespace Kyoo.Controllers } } - public virtual async Task Edit(T edited, bool resetOld) { if (edited == null) @@ -170,83 +166,11 @@ namespace Kyoo.Controllers if (old == null) throw new ItemNotFound($"No resource found with the ID {edited.ID}."); - List navigations = typeof(T).GetProperties() - .Where(x => x.GetCustomAttribute() != null) - .ToList(); - List links = typeof(T).GetProperties() - .Select(x => x.GetCustomAttribute()?.Relation) - .Where(x => x != null) - .ToList(); - - // This handle every links so every Many to Many - // TODO should handle non links value (One to Many) - foreach (string relationName in links) - { - PropertyInfo relation = navigations.Find(x => x.Name == relationName); - navigations.Remove(relation); - - if (relation!.GetValue(edited) == null && !resetOld) - continue; - - await _library.Load(old, relation.Name); - IEnumerable oValues = (relation.GetValue(old) as IEnumerable)!.Cast(); - List nValues = (relation.GetValue(edited) as IEnumerable)?.Cast().ToList(); - - foreach (IResource x in oValues) - { - int nIndex = nValues?.FindIndex(y => Utility.ResourceEquals(x, y)) ?? -1; - if (nIndex == -1 || resetOld) - await _linkManager.Delete(old, x); - else - nValues!.RemoveAt(nIndex); - } - - await nValues.ForEachAsync(x => _linkManager.Add(old, x)); - } - - // This handle every X to One - foreach (PropertyInfo relation in navigations) - { - IResource oValues = relation.GetValue(old) as IResource; - IResource nValues = relation.GetValue(edited) as IResource; - - if (nValues == null && !resetOld) - continue; - - if (false) // TODO change if false with if relation is casquade delete - await _library.Delete(oValue); - relation.SetValue(old, nValues); - // TODO call create if not exist - // TODO change relationID to the new value ID. - } - - - // This handle every One to Many or Many to One - foreach (PropertyInfo navigation in navigations) - { - if (resetOld || navigation.GetValue(edited) != default) - { - // TODO only works for X To One and not X to Many - await _library.Value.Load(old, navigation.Name); - object value = navigation.GetValue(old); - if (value is IEnumerable list) // TODO handle externalIds & PeopleRoles (implement Link<> for those) - list.ForEach(x => _library.Value.Delete(x)); - else if (value is IResource resource) - _library.Value.Delete(resource); - } - - - if (navigation.GetCustomAttribute() == null) - { - navigation.SetValue(edited, default); - continue; - } - } - + + await EditRelations(old, edited); if (resetOld) Utility.Nullify(old); - Utility.Complete(old, edited); - await Validate(old); + Utility.Complete(old, edited, x => x.GetCustomAttribute() != null); await Database.SaveChangesAsync(); return old; } @@ -255,13 +179,13 @@ namespace Kyoo.Controllers Database.ChangeTracker.LazyLoadingEnabled = lazyLoading; } } - - protected bool ShouldValidate(T2 value) + + protected virtual Task EditRelations(T resource, T newValues) { - return value != null && Database.Entry(value).State == EntityState.Detached; + return Validate(resource); } - - protected virtual Task Validate(T resource) + + private Task Validate(T resource) { if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("Resource can't have null as a slug."); @@ -282,7 +206,7 @@ namespace Kyoo.Controllers } return Task.CompletedTask; } - + public virtual async Task Delete(int id) { T resource = await Get(id); diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index ee53f331..9a25a2b1 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -153,25 +154,31 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; + obj.Tracks = await obj.Tracks.SelectAsync(x => _tracks.CreateIfNotExists(x, true)).ToListAsync(); obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); - obj.Tracks.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); return obj; } - protected override async Task Validate(Episode resource) + protected override async Task EditRelations(Episode resource, Episode changed) { if (resource.ShowID <= 0) throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {resource.ShowID})."); - await base.Validate(resource); - - // if (resource.Tracks != null) - // { - // resource.Tracks = await resource.Tracks - // .SelectAsync(x => _tracks.CreateIfNotExists(x, true)) - // .ToListAsync(); - // } + await base.EditRelations(resource, changed); + + ICollection oldTracks = resource.Tracks; + resource.Tracks = await changed.Tracks.SelectAsync(async track => + { + Track oldValue = oldTracks?.FirstOrDefault(x => Utility.ResourceEquals(track, x)); + if (oldValue == null) + return await _tracks.CreateIfNotExists(track, true); + oldTracks.Remove(oldValue); + return oldValue; + }) + .ToListAsync(); + foreach (Track x in oldTracks) + await _tracks.Delete(x); if (resource.ExternalIDs != null) { From 28bb719b06569e99aa5c625fbad71e29393e6c2b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Mar 2021 09:49:48 +0100 Subject: [PATCH 38/54] Fixing episode & library's repository --- Kyoo.CommonAPI/LocalRepository.cs | 3 ++- .../Repositories/EpisodeRepository.cs | 21 +++++++++---------- .../Repositories/LibraryItemRepository.cs | 2 +- .../Repositories/LibraryRepository.cs | 17 ++++++++------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index b35bb041..f614ede5 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -171,6 +171,7 @@ namespace Kyoo.Controllers if (resetOld) Utility.Nullify(old); Utility.Complete(old, edited, x => x.GetCustomAttribute() != null); + await Validate(old); await Database.SaveChangesAsync(); return old; } @@ -185,7 +186,7 @@ namespace Kyoo.Controllers return Validate(resource); } - private Task Validate(T resource) + protected virtual Task Validate(T resource) { if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("Resource can't have null as a slug."); diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 9a25a2b1..27cd155a 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -179,13 +178,15 @@ namespace Kyoo.Controllers .ToListAsync(); foreach (Track x in oldTracks) await _tracks.Delete(x); + + resource.ExternalIDs = changed.ExternalIDs; + } - if (resource.ExternalIDs != null) - { - foreach (MetadataID link in resource.ExternalIDs) - if (ShouldValidate(link)) - link.Provider = await _providers.CreateIfNotExists(link.Provider, true); - } + protected override async Task Validate(Episode resource) + { + await base.Validate(resource); + await resource.ExternalIDs.ForEachAsync(async id => + id.Provider = await _providers.CreateIfNotExists(id.Provider, true)); } public async Task Delete(string showSlug, int seasonNumber, int episodeNumber) @@ -200,10 +201,8 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - // await obj.Tracks.ForEachAsync(x => _tracks.CreateIfNotExists(x, true)); - if (obj.ExternalIDs != null) - foreach (MetadataID entry in obj.ExternalIDs) - _database.Entry(entry).State = EntityState.Deleted; + await obj.Tracks.ForEachAsync(x => _tracks.Delete(x)); + obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted); await _database.SaveChangesAsync(); } } diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 20f7139f..4d49a6f5 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -110,7 +110,7 @@ namespace Kyoo.Controllers throw new InvalidOperationException(); } public override Task Edit(LibraryItem obj, bool reset) => throw new InvalidOperationException(); - protected override Task Validate(LibraryItem resource) => throw new InvalidOperationException(); + protected override Task EditRelations(LibraryItem _, LibraryItem _2) => throw new InvalidOperationException(); public override Task Delete(int id) => throw new InvalidOperationException(); public override Task Delete(string slug) => throw new InvalidOperationException(); public override Task Delete(LibraryItem obj) => throw new InvalidOperationException(); diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 9278c563..93996a82 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -62,6 +62,14 @@ namespace Kyoo.Controllers } protected override async Task Validate(Library resource) + { + await base.Validate(resource); + resource.Providers = await resource.Providers + .SelectAsync(x => _providers.CreateIfNotExists(x, true)) + .ToListAsync(); + } + + protected override Task EditRelations(Library resource, Library changed) { if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("The library's slug must be set and not empty"); @@ -70,14 +78,7 @@ namespace Kyoo.Controllers if (resource.Paths == null || !resource.Paths.Any()) throw new ArgumentException("The library should have a least one path."); - await base.Validate(resource); - - if (resource.Providers != null) - { - resource.Providers = await resource.Providers - .SelectAsync(x => _providers.CreateIfNotExists(x, true)) - .ToListAsync(); - } + return base.EditRelations(resource, changed); } public override async Task Delete(Library obj) From a74a06913ce89ac107bef4008000814c2b6eecd0 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Mar 2021 10:01:02 +0100 Subject: [PATCH 39/54] Fixing people repositroy --- Kyoo.CommonAPI/LocalRepository.cs | 2 +- .../Repositories/LibraryItemRepository.cs | 2 +- .../Repositories/PeopleRepository.cs | 26 ++++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index f614ede5..1a2f29ea 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -181,7 +181,7 @@ namespace Kyoo.Controllers } } - protected virtual Task EditRelations(T resource, T newValues) + protected virtual Task EditRelations(T resource, T changed) { return Validate(resource); } diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 4d49a6f5..701c32ff 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -110,7 +110,7 @@ namespace Kyoo.Controllers throw new InvalidOperationException(); } public override Task Edit(LibraryItem obj, bool reset) => throw new InvalidOperationException(); - protected override Task EditRelations(LibraryItem _, LibraryItem _2) => throw new InvalidOperationException(); + protected override Task EditRelations(LibraryItem _, LibraryItem changed) => throw new InvalidOperationException(); public override Task Delete(int id) => throw new InvalidOperationException(); public override Task Delete(string slug) => throw new InvalidOperationException(); public override Task Delete(LibraryItem obj) => throw new InvalidOperationException(); diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index d5b15fea..3d462f87 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -71,25 +71,27 @@ namespace Kyoo.Controllers protected override async Task Validate(People resource) { await base.Validate(resource); - - if (resource.ExternalIDs != null) - foreach (MetadataID link in resource.ExternalIDs) - if (ShouldValidate(link)) - link.Provider = await _providers.CreateIfNotExists(link.Provider, true); + await resource.ExternalIDs.ForEachAsync(async id => + id.Provider = await _providers.CreateIfNotExists(id.Provider, true)); + await resource.Roles.ForEachAsync(async role => + role.Show = await _shows.Value.CreateIfNotExists(role.Show, true)); } - + + protected override async Task EditRelations(People resource, People changed) + { + await base.EditRelations(resource, changed); + resource.Roles = changed.Roles; + resource.ExternalIDs = changed.ExternalIDs; + } + public override async Task Delete(People obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - if (obj.ExternalIDs != null) - foreach (MetadataID entry in obj.ExternalIDs) - _database.Entry(entry).State = EntityState.Deleted; - if (obj.Roles != null) - foreach (PeopleRole link in obj.Roles) - _database.Entry(link).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(); } From 25edb3977a27620e6925cfe06fd671182a772b56 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Mar 2021 10:14:51 +0100 Subject: [PATCH 40/54] Fixing season's repository --- Kyoo.Common/Models/Resources/ProviderID.cs | 1 + Kyoo.Common/Utility.cs | 6 +- Kyoo.CommonAPI/LocalRepository.cs | 2 +- .../Repositories/ProviderRepository.cs | 3 +- .../Repositories/SeasonRepository.cs | 23 +- Kyoo/Models/DatabaseContext.cs | 4 + .../20210312234147_Initial.Designer.cs | 776 ------------------ .../Internal/20210312234147_Initial.cs | 604 -------------- .../Internal/DatabaseContextModelSnapshot.cs | 774 ----------------- 9 files changed, 20 insertions(+), 2173 deletions(-) delete mode 100644 Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs delete mode 100644 Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs delete mode 100644 Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs diff --git a/Kyoo.Common/Models/Resources/ProviderID.cs b/Kyoo.Common/Models/Resources/ProviderID.cs index 85374979..6500f9f7 100644 --- a/Kyoo.Common/Models/Resources/ProviderID.cs +++ b/Kyoo.Common/Models/Resources/ProviderID.cs @@ -14,6 +14,7 @@ namespace Kyoo.Models #if ENABLE_INTERNAL_LINKS [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } + [SerializeIgnore] public virtual ICollection MetadataLinks { get; set; } #endif public ProviderID() { } diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index d7be7dec..f672d598 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -124,7 +124,7 @@ namespace Kyoo return first; } - public static T Complete(T first, T second, Func ignore = null) + public static T Complete(T first, T second, Func where = null) { if (first == null) throw new ArgumentNullException(nameof(first)); @@ -136,8 +136,8 @@ namespace Kyoo .Where(x => x.CanRead && x.CanWrite && Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null); - if (ignore != null) - properties = properties.Where(ignore); + if (where != null) + properties = properties.Where(where); foreach (PropertyInfo property in properties) { diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 1a2f29ea..704f74b5 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -170,7 +170,7 @@ namespace Kyoo.Controllers await EditRelations(old, edited); if (resetOld) Utility.Nullify(old); - Utility.Complete(old, edited, x => x.GetCustomAttribute() != null); + Utility.Complete(old, edited, x => x.GetCustomAttribute() == null); await Validate(old); await Database.SaveChangesAsync(); return old; diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 2e2e7c2c..79cc80b1 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -32,7 +32,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated provider (slug {obj.Slug} already exists)."); return obj; } @@ -43,7 +42,7 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - // TODO handle ExternalID deletion when they refer to this providerID. + obj.MetadataLinks.ForEach(x => _database.Entry(x).State = EntityState.Deleted); await _database.SaveChangesAsync(); } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index c1344ed6..7678b64c 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -138,15 +138,16 @@ namespace Kyoo.Controllers throw new InvalidOperationException($"Can't store a season not related to any show (showID: {resource.ShowID})."); await base.Validate(resource); - - if (resource.ExternalIDs != null) - { - foreach (MetadataID link in resource.ExternalIDs) - if (ShouldValidate(link)) - link.Provider = await _providers.CreateIfNotExists(link.Provider, true); - } + await resource.ExternalIDs.ForEachAsync(async id => + id.Provider = await _providers.CreateIfNotExists(id.Provider, true)); } - + + protected override Task EditRelations(Season resource, Season changed) + { + resource.ExternalIDs = changed.ExternalIDs; + return base.EditRelations(resource, changed); + } + public async Task Delete(string showSlug, int seasonNumber) { Season obj = await Get(showSlug, seasonNumber); @@ -159,11 +160,7 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - - if (obj.ExternalIDs != null) - foreach (MetadataID entry in obj.ExternalIDs) - _database.Entry(entry).State = EntityState.Deleted; - + obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted); await _database.SaveChangesAsync(); if (obj.Episodes != null) diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 125f0ed8..e03f6677 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -145,6 +145,10 @@ namespace Kyoo .HasOne(x => x.People) .WithMany(x => x.ExternalIDs) .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .HasOne(x => x.Provider) + .WithMany(x => x.MetadataLinks) + .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs deleted file mode 100644 index 8817046e..00000000 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs +++ /dev/null @@ -1,776 +0,0 @@ -// -using System; -using Kyoo; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Models.DatabaseMigrations.Internal -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20210312234147_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) - .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) - .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.3") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("Kyoo.Models.Collection", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Collections"); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AbsoluteNumber") - .HasColumnType("integer"); - - b.Property("EpisodeNumber") - .HasColumnType("integer"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("ReleaseDate") - .HasColumnType("timestamp without time zone"); - - b.Property("Runtime") - .HasColumnType("integer"); - - b.Property("SeasonID") - .HasColumnType("integer"); - - b.Property("SeasonNumber") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Thumb") - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("SeasonID"); - - b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") - .IsUnique(); - - b.ToTable("Episodes"); - }); - - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Genres"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Paths") - .HasColumnType("text[]"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Libraries"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("DataID") - .HasColumnType("text"); - - b.Property("EpisodeID") - .HasColumnType("integer"); - - b.Property("Link") - .HasColumnType("text"); - - b.Property("PeopleID") - .HasColumnType("integer"); - - b.Property("ProviderID") - .HasColumnType("integer"); - - b.Property("SeasonID") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("EpisodeID"); - - b.HasIndex("PeopleID"); - - b.HasIndex("ProviderID"); - - b.HasIndex("SeasonID"); - - b.HasIndex("ShowID"); - - b.ToTable("MetadataIds"); - }); - - modelBuilder.Entity("Kyoo.Models.People", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("People"); - }); - - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("PeopleID") - .HasColumnType("integer"); - - b.Property("Role") - .HasColumnType("text"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("PeopleID"); - - b.HasIndex("ShowID"); - - b.ToTable("PeopleRoles"); - }); - - modelBuilder.Entity("Kyoo.Models.ProviderID", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Logo") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Providers"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("SeasonNumber") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("ShowID", "SeasonNumber") - .IsUnique(); - - b.ToTable("Seasons"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Aliases") - .HasColumnType("text[]"); - - b.Property("Backdrop") - .HasColumnType("text"); - - b.Property("EndYear") - .HasColumnType("integer"); - - b.Property("IsMovie") - .HasColumnType("boolean"); - - b.Property("Logo") - .HasColumnType("text"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("StartYear") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("StudioID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("TrailerUrl") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.HasIndex("StudioID"); - - b.ToTable("Shows"); - }); - - modelBuilder.Entity("Kyoo.Models.Studio", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Studios"); - }); - - modelBuilder.Entity("Kyoo.Models.Track", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Codec") - .HasColumnType("text"); - - b.Property("EpisodeID") - .HasColumnType("integer"); - - b.Property("IsDefault") - .HasColumnType("boolean"); - - b.Property("IsExternal") - .HasColumnType("boolean"); - - b.Property("IsForced") - .HasColumnType("boolean"); - - b.Property("Language") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("EpisodeID"); - - b.ToTable("Tracks"); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.HasOne("Kyoo.Models.Season", "Season") - .WithMany("Episodes") - .HasForeignKey("SeasonID"); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("Episodes") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.ProviderID", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "Episode") - .WithMany("ExternalIDs") - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.People", "People") - .WithMany("ExternalIDs") - .HasForeignKey("PeopleID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.ProviderID", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Season", "Season") - .WithMany("ExternalIDs") - .HasForeignKey("SeasonID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("ExternalIDs") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Episode"); - - b.Navigation("People"); - - b.Navigation("Provider"); - - b.Navigation("Season"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => - { - b.HasOne("Kyoo.Models.People", "People") - .WithMany("Roles") - .HasForeignKey("PeopleID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("People") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("People"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("Seasons") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.HasOne("Kyoo.Models.Studio", "Studio") - .WithMany("Shows") - .HasForeignKey("StudioID"); - - b.Navigation("Studio"); - }); - - modelBuilder.Entity("Kyoo.Models.Track", b => - { - b.HasOne("Kyoo.Models.Episode", "Episode") - .WithMany("Tracks") - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Episode"); - }); - - modelBuilder.Entity("Kyoo.Models.Collection", b => - { - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Tracks"); - }); - - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.People", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Roles"); - }); - - modelBuilder.Entity("Kyoo.Models.ProviderID", b => - { - b.Navigation("LibraryLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - - b.Navigation("People"); - - b.Navigation("Seasons"); - }); - - modelBuilder.Entity("Kyoo.Models.Studio", b => - { - b.Navigation("Shows"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs deleted file mode 100644 index 4b007b7f..00000000 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs +++ /dev/null @@ -1,604 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Models.DatabaseMigrations.Internal -{ - 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", "finished,airing,planned,unknown") - .Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,font"); - - 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), - Poster = table.Column(type: "text", 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), - Poster = table.Column(type: "text", 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), - Logo = table.Column(type: "text", 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: "Link", - columns: table => new - { - FirstID = table.Column(type: "integer", nullable: false), - SecondID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_Link_Collections_SecondID", - column: x => x.SecondID, - principalTable: "Collections", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Link_Libraries_FirstID", - column: x => x.FirstID, - principalTable: "Libraries", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Link", - columns: table => new - { - FirstID = table.Column(type: "integer", nullable: false), - SecondID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_Link_Libraries_FirstID", - column: x => x.FirstID, - principalTable: "Libraries", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Link_Providers_SecondID", - column: x => x.SecondID, - 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: "integer", nullable: true), - TrailerUrl = table.Column(type: "text", nullable: true), - StartYear = table.Column(type: "integer", nullable: true), - EndYear = table.Column(type: "integer", nullable: true), - Poster = table.Column(type: "text", nullable: true), - Logo = table.Column(type: "text", nullable: true), - Backdrop = table.Column(type: "text", nullable: true), - IsMovie = table.Column(type: "boolean", nullable: false), - StudioID = table.Column(type: "integer", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Shows", x => x.ID); - table.ForeignKey( - name: "FK_Shows_Studios_StudioID", - column: x => x.StudioID, - principalTable: "Studios", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Link", - columns: table => new - { - FirstID = table.Column(type: "integer", nullable: false), - SecondID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_Link_Collections_FirstID", - column: x => x.FirstID, - principalTable: "Collections", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Link_Shows_SecondID", - column: x => x.SecondID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Link", - columns: table => new - { - FirstID = table.Column(type: "integer", nullable: false), - SecondID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_Link_Libraries_FirstID", - column: x => x.FirstID, - principalTable: "Libraries", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Link_Shows_SecondID", - column: x => x.SecondID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Link", - columns: table => new - { - FirstID = table.Column(type: "integer", nullable: false), - SecondID = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); - table.ForeignKey( - name: "FK_Link_Genres_SecondID", - column: x => x.SecondID, - principalTable: "Genres", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Link_Shows_FirstID", - column: x => x.FirstID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "PeopleRoles", - columns: table => new - { - ID = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - PeopleID = table.Column(type: "integer", nullable: false), - ShowID = table.Column(type: "integer", nullable: false), - Role = table.Column(type: "text", nullable: true), - Type = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_PeopleRoles", x => x.ID); - table.ForeignKey( - name: "FK_PeopleRoles_People_PeopleID", - column: x => x.PeopleID, - principalTable: "People", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_PeopleRoles_Shows_ShowID", - column: x => x.ShowID, - 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), - ShowID = table.Column(type: "integer", nullable: false), - SeasonNumber = table.Column(type: "integer", nullable: false), - Title = table.Column(type: "text", nullable: true), - Overview = table.Column(type: "text", nullable: true), - Year = table.Column(type: "integer", nullable: true), - Poster = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Seasons", x => x.ID); - table.ForeignKey( - name: "FK_Seasons_Shows_ShowID", - column: x => x.ShowID, - 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), - ShowID = table.Column(type: "integer", nullable: false), - SeasonID = table.Column(type: "integer", nullable: true), - SeasonNumber = table.Column(type: "integer", nullable: false), - EpisodeNumber = table.Column(type: "integer", nullable: false), - AbsoluteNumber = table.Column(type: "integer", nullable: false), - Path = table.Column(type: "text", nullable: true), - Thumb = table.Column(type: "text", nullable: true), - Title = table.Column(type: "text", nullable: true), - Overview = table.Column(type: "text", nullable: true), - ReleaseDate = table.Column(type: "timestamp without time zone", nullable: true), - Runtime = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Episodes", x => x.ID); - table.ForeignKey( - name: "FK_Episodes_Seasons_SeasonID", - column: x => x.SeasonID, - principalTable: "Seasons", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Episodes_Shows_ShowID", - column: x => x.ShowID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "MetadataIds", - columns: table => new - { - ID = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ProviderID = table.Column(type: "integer", nullable: false), - ShowID = table.Column(type: "integer", nullable: true), - EpisodeID = table.Column(type: "integer", nullable: true), - SeasonID = table.Column(type: "integer", nullable: true), - PeopleID = table.Column(type: "integer", nullable: true), - DataID = table.Column(type: "text", nullable: true), - Link = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MetadataIds", x => x.ID); - table.ForeignKey( - name: "FK_MetadataIds_Episodes_EpisodeID", - column: x => x.EpisodeID, - principalTable: "Episodes", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MetadataIds_People_PeopleID", - column: x => x.PeopleID, - principalTable: "People", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MetadataIds_Providers_ProviderID", - column: x => x.ProviderID, - principalTable: "Providers", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MetadataIds_Seasons_SeasonID", - column: x => x.SeasonID, - principalTable: "Seasons", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MetadataIds_Shows_ShowID", - column: x => x.ShowID, - principalTable: "Shows", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Tracks", - columns: table => new - { - ID = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - EpisodeID = table.Column(type: "integer", nullable: false), - IsDefault = table.Column(type: "boolean", nullable: false), - IsForced = table.Column(type: "boolean", nullable: false), - IsExternal = table.Column(type: "boolean", nullable: false), - Title = table.Column(type: "text", nullable: true), - Language = table.Column(type: "text", nullable: true), - Codec = table.Column(type: "text", nullable: true), - Path = table.Column(type: "text", nullable: true), - Type = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Tracks", x => x.ID); - table.ForeignKey( - name: "FK_Tracks_Episodes_EpisodeID", - column: x => x.EpisodeID, - principalTable: "Episodes", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Collections_Slug", - table: "Collections", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Episodes_SeasonID", - table: "Episodes", - column: "SeasonID"); - - migrationBuilder.CreateIndex( - name: "IX_Episodes_ShowID_SeasonNumber_EpisodeNumber_AbsoluteNumber", - table: "Episodes", - columns: new[] { "ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber" }, - 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_SecondID", - table: "Link", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", - column: "SecondID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_EpisodeID", - table: "MetadataIds", - column: "EpisodeID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_PeopleID", - table: "MetadataIds", - column: "PeopleID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_ProviderID", - table: "MetadataIds", - column: "ProviderID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_SeasonID", - table: "MetadataIds", - column: "SeasonID"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataIds_ShowID", - table: "MetadataIds", - column: "ShowID"); - - migrationBuilder.CreateIndex( - name: "IX_People_Slug", - table: "People", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_PeopleRoles_PeopleID", - table: "PeopleRoles", - column: "PeopleID"); - - migrationBuilder.CreateIndex( - name: "IX_PeopleRoles_ShowID", - table: "PeopleRoles", - column: "ShowID"); - - migrationBuilder.CreateIndex( - name: "IX_Providers_Slug", - table: "Providers", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Seasons_ShowID_SeasonNumber", - table: "Seasons", - columns: new[] { "ShowID", "SeasonNumber" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Shows_Slug", - table: "Shows", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Shows_StudioID", - table: "Shows", - column: "StudioID"); - - migrationBuilder.CreateIndex( - name: "IX_Studios_Slug", - table: "Studios", - column: "Slug", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Tracks_EpisodeID", - table: "Tracks", - column: "EpisodeID"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Link"); - - migrationBuilder.DropTable( - name: "Link"); - - migrationBuilder.DropTable( - name: "Link"); - - migrationBuilder.DropTable( - name: "Link"); - - migrationBuilder.DropTable( - name: "Link"); - - migrationBuilder.DropTable( - name: "MetadataIds"); - - migrationBuilder.DropTable( - name: "PeopleRoles"); - - migrationBuilder.DropTable( - name: "Tracks"); - - migrationBuilder.DropTable( - name: "Collections"); - - migrationBuilder.DropTable( - name: "Libraries"); - - migrationBuilder.DropTable( - name: "Genres"); - - migrationBuilder.DropTable( - name: "Providers"); - - migrationBuilder.DropTable( - name: "People"); - - migrationBuilder.DropTable( - name: "Episodes"); - - migrationBuilder.DropTable( - name: "Seasons"); - - migrationBuilder.DropTable( - name: "Shows"); - - migrationBuilder.DropTable( - name: "Studios"); - } - } -} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs deleted file mode 100644 index fb3f4599..00000000 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ /dev/null @@ -1,774 +0,0 @@ -// -using System; -using Kyoo; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -namespace Kyoo.Models.DatabaseMigrations.Internal -{ - [DbContext(typeof(DatabaseContext))] - partial class DatabaseContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) - .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) - .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) - .HasAnnotation("Relational:MaxIdentifierLength", 63) - .HasAnnotation("ProductVersion", "5.0.3") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - modelBuilder.Entity("Kyoo.Models.Collection", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Collections"); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("AbsoluteNumber") - .HasColumnType("integer"); - - b.Property("EpisodeNumber") - .HasColumnType("integer"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("ReleaseDate") - .HasColumnType("timestamp without time zone"); - - b.Property("Runtime") - .HasColumnType("integer"); - - b.Property("SeasonID") - .HasColumnType("integer"); - - b.Property("SeasonNumber") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Thumb") - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("SeasonID"); - - b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") - .IsUnique(); - - b.ToTable("Episodes"); - }); - - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Genres"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Paths") - .HasColumnType("text[]"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Libraries"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.Property("FirstID") - .HasColumnType("integer"); - - b.Property("SecondID") - .HasColumnType("integer"); - - b.HasKey("FirstID", "SecondID"); - - b.HasIndex("SecondID"); - - b.ToTable("Link"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("DataID") - .HasColumnType("text"); - - b.Property("EpisodeID") - .HasColumnType("integer"); - - b.Property("Link") - .HasColumnType("text"); - - b.Property("PeopleID") - .HasColumnType("integer"); - - b.Property("ProviderID") - .HasColumnType("integer"); - - b.Property("SeasonID") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("EpisodeID"); - - b.HasIndex("PeopleID"); - - b.HasIndex("ProviderID"); - - b.HasIndex("SeasonID"); - - b.HasIndex("ShowID"); - - b.ToTable("MetadataIds"); - }); - - modelBuilder.Entity("Kyoo.Models.People", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("People"); - }); - - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("PeopleID") - .HasColumnType("integer"); - - b.Property("Role") - .HasColumnType("text"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("PeopleID"); - - b.HasIndex("ShowID"); - - b.ToTable("PeopleRoles"); - }); - - modelBuilder.Entity("Kyoo.Models.ProviderID", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Logo") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Providers"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("SeasonNumber") - .HasColumnType("integer"); - - b.Property("ShowID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("ShowID", "SeasonNumber") - .IsUnique(); - - b.ToTable("Seasons"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Aliases") - .HasColumnType("text[]"); - - b.Property("Backdrop") - .HasColumnType("text"); - - b.Property("EndYear") - .HasColumnType("integer"); - - b.Property("IsMovie") - .HasColumnType("boolean"); - - b.Property("Logo") - .HasColumnType("text"); - - b.Property("Overview") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Poster") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("StartYear") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("StudioID") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("TrailerUrl") - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.HasIndex("StudioID"); - - b.ToTable("Shows"); - }); - - modelBuilder.Entity("Kyoo.Models.Studio", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ID"); - - b.HasIndex("Slug") - .IsUnique(); - - b.ToTable("Studios"); - }); - - modelBuilder.Entity("Kyoo.Models.Track", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - - b.Property("Codec") - .HasColumnType("text"); - - b.Property("EpisodeID") - .HasColumnType("integer"); - - b.Property("IsDefault") - .HasColumnType("boolean"); - - b.Property("IsExternal") - .HasColumnType("boolean"); - - b.Property("IsForced") - .HasColumnType("boolean"); - - b.Property("Language") - .HasColumnType("text"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("ID"); - - b.HasIndex("EpisodeID"); - - b.ToTable("Tracks"); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.HasOne("Kyoo.Models.Season", "Season") - .WithMany("Episodes") - .HasForeignKey("SeasonID"); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("Episodes") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Collection", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("CollectionLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("CollectionLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Collection", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ProviderLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.ProviderID", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Library", "First") - .WithMany("ShowLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Second") - .WithMany("LibraryLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.Link", b => - { - b.HasOne("Kyoo.Models.Show", "First") - .WithMany("GenreLinks") - .HasForeignKey("FirstID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Genre", "Second") - .WithMany("ShowLinks") - .HasForeignKey("SecondID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("First"); - - b.Navigation("Second"); - }); - - modelBuilder.Entity("Kyoo.Models.MetadataID", b => - { - b.HasOne("Kyoo.Models.Episode", "Episode") - .WithMany("ExternalIDs") - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.People", "People") - .WithMany("ExternalIDs") - .HasForeignKey("PeopleID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.ProviderID", "Provider") - .WithMany() - .HasForeignKey("ProviderID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Season", "Season") - .WithMany("ExternalIDs") - .HasForeignKey("SeasonID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("ExternalIDs") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("Episode"); - - b.Navigation("People"); - - b.Navigation("Provider"); - - b.Navigation("Season"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.PeopleRole", b => - { - b.HasOne("Kyoo.Models.People", "People") - .WithMany("Roles") - .HasForeignKey("PeopleID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("People") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("People"); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.HasOne("Kyoo.Models.Show", "Show") - .WithMany("Seasons") - .HasForeignKey("ShowID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Show"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.HasOne("Kyoo.Models.Studio", "Studio") - .WithMany("Shows") - .HasForeignKey("StudioID"); - - b.Navigation("Studio"); - }); - - modelBuilder.Entity("Kyoo.Models.Track", b => - { - b.HasOne("Kyoo.Models.Episode", "Episode") - .WithMany("Tracks") - .HasForeignKey("EpisodeID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Episode"); - }); - - modelBuilder.Entity("Kyoo.Models.Collection", b => - { - b.Navigation("LibraryLinks"); - - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Episode", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Tracks"); - }); - - modelBuilder.Entity("Kyoo.Models.Genre", b => - { - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Library", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("ProviderLinks"); - - b.Navigation("ShowLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.People", b => - { - b.Navigation("ExternalIDs"); - - b.Navigation("Roles"); - }); - - modelBuilder.Entity("Kyoo.Models.ProviderID", b => - { - b.Navigation("LibraryLinks"); - }); - - modelBuilder.Entity("Kyoo.Models.Season", b => - { - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - }); - - modelBuilder.Entity("Kyoo.Models.Show", b => - { - b.Navigation("CollectionLinks"); - - b.Navigation("Episodes"); - - b.Navigation("ExternalIDs"); - - b.Navigation("GenreLinks"); - - b.Navigation("LibraryLinks"); - - b.Navigation("People"); - - b.Navigation("Seasons"); - }); - - modelBuilder.Entity("Kyoo.Models.Studio", b => - { - b.Navigation("Shows"); - }); -#pragma warning restore 612, 618 - } - } -} From 93d271b192b09e5e123993d230a713e9025eabfc Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Mar 2021 11:22:34 +0100 Subject: [PATCH 41/54] Cleaning up every repository's edit & adding resetOld --- Kyoo.CommonAPI/LocalRepository.cs | 8 +- .../Repositories/EpisodeRepository.cs | 48 +- .../Repositories/LibraryItemRepository.cs | 1 - .../Repositories/LibraryRepository.cs | 9 +- .../Repositories/PeopleRepository.cs | 8 +- .../Repositories/SeasonRepository.cs | 6 +- .../Repositories/ShowRepository.cs | 49 +- .../Repositories/StudioRepository.cs | 4 - .../Repositories/TrackRepository.cs | 9 +- .../20210316095337_Initial.Designer.cs | 778 ++++++++++++++++++ .../Internal/20210316095337_Initial.cs | 604 ++++++++++++++ .../Internal/DatabaseContextModelSnapshot.cs | 776 +++++++++++++++++ 12 files changed, 2236 insertions(+), 64 deletions(-) create mode 100644 Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.Designer.cs create mode 100644 Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.cs create mode 100644 Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 704f74b5..874272aa 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -162,14 +162,12 @@ namespace Kyoo.Controllers try { T old = await GetWithTracking(edited.ID); - if (old == null) throw new ItemNotFound($"No resource found with the ID {edited.ID}."); - - - await EditRelations(old, edited); + if (resetOld) Utility.Nullify(old); + await EditRelations(old, edited, resetOld); Utility.Complete(old, edited, x => x.GetCustomAttribute() == null); await Validate(old); await Database.SaveChangesAsync(); @@ -181,7 +179,7 @@ namespace Kyoo.Controllers } } - protected virtual Task EditRelations(T resource, T changed) + protected virtual Task EditRelations(T resource, T changed, bool resetOld) { return Validate(resource); } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 27cd155a..a68161c2 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -153,33 +153,45 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.Tracks = await obj.Tracks.SelectAsync(x => _tracks.CreateIfNotExists(x, true)).ToListAsync(); obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); + obj.Tracks = await obj.Tracks.SelectAsync(x => + { + x.Episode = obj; + x.EpisodeID = obj.ID; + return _tracks.CreateIfNotExists(x, true); + }).ToListAsync(); return obj; } - protected override async Task EditRelations(Episode resource, Episode changed) + protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld) { if (resource.ShowID <= 0) throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {resource.ShowID})."); - await base.EditRelations(resource, changed); - - ICollection oldTracks = resource.Tracks; - resource.Tracks = await changed.Tracks.SelectAsync(async track => - { - Track oldValue = oldTracks?.FirstOrDefault(x => Utility.ResourceEquals(track, x)); - if (oldValue == null) - return await _tracks.CreateIfNotExists(track, true); - oldTracks.Remove(oldValue); - return oldValue; - }) - .ToListAsync(); - foreach (Track x in oldTracks) - await _tracks.Delete(x); - - resource.ExternalIDs = changed.ExternalIDs; + await base.EditRelations(resource, changed, resetOld); + + if (changed.Tracks != null || resetOld) + { + ICollection oldTracks = await _tracks.GetAll(x => x.EpisodeID == resource.ID); + resource.Tracks = await changed.Tracks.SelectAsync(async track => + { + Track oldValue = oldTracks?.FirstOrDefault(x => Utility.ResourceEquals(track, x)); + if (oldValue == null) + return await _tracks.CreateIfNotExists(track, true); + oldTracks.Remove(oldValue); + return oldValue; + }) + .ToListAsync(); + foreach (Track x in oldTracks) + await _tracks.Delete(x); + } + + 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) diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 701c32ff..57f293b5 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -110,7 +110,6 @@ namespace Kyoo.Controllers throw new InvalidOperationException(); } public override Task Edit(LibraryItem obj, bool reset) => throw new InvalidOperationException(); - protected override Task EditRelations(LibraryItem _, LibraryItem changed) => throw new InvalidOperationException(); public override Task Delete(int id) => throw new InvalidOperationException(); public override Task Delete(string slug) => throw new InvalidOperationException(); public override Task Delete(LibraryItem obj) => throw new InvalidOperationException(); diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 93996a82..70e6d77e 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -69,7 +69,7 @@ namespace Kyoo.Controllers .ToListAsync(); } - protected override Task EditRelations(Library resource, Library changed) + protected override async Task EditRelations(Library resource, Library changed, bool resetOld) { if (string.IsNullOrEmpty(resource.Slug)) throw new ArgumentException("The library's slug must be set and not empty"); @@ -77,8 +77,11 @@ namespace Kyoo.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."); - - return base.EditRelations(resource, changed); + + if (changed.Providers != null || resetOld) + await Database.Entry(resource).Collection(x => x.Providers).LoadAsync(); + resource.Providers = changed.Providers; + await base.EditRelations(resource, changed, resetOld); } public override async Task Delete(Library obj) diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 3d462f87..58d0c777 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -77,11 +77,15 @@ namespace Kyoo.Controllers role.Show = await _shows.Value.CreateIfNotExists(role.Show, true)); } - protected override async Task EditRelations(People resource, People changed) + protected override async Task EditRelations(People resource, People changed, bool resetOld) { - await base.EditRelations(resource, changed); + if (changed.Roles != null || resetOld) + 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; + await base.EditRelations(resource, changed, resetOld); } public override async Task Delete(People obj) diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 7678b64c..ba076c02 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -142,10 +142,12 @@ namespace Kyoo.Controllers id.Provider = await _providers.CreateIfNotExists(id.Provider, true)); } - protected override Task EditRelations(Season resource, Season changed) + protected override async Task EditRelations(Season resource, Season changed, bool resetOld) { + if (changed.ExternalIDs != null || resetOld) + await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); resource.ExternalIDs = changed.ExternalIDs; - return base.EditRelations(resource, changed); + await base.EditRelations(resource, changed, resetOld); } public async Task Delete(string showSlug, int seasonNumber) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 545dd880..a820a02e 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -99,28 +99,35 @@ namespace Kyoo.Controllers protected override async Task Validate(Show resource) { await base.Validate(resource); - - if (ShouldValidate(resource.Studio)) - resource.Studio = await _studios.CreateIfNotExists(resource.Studio, true); - - if (resource.Genres != null) - { - resource.Genres = await resource.Genres - .SelectAsync(x => _genres.CreateIfNotExists(x, true)) - .ToListAsync(); - } - - if (resource.People != null) - foreach (PeopleRole link in resource.People) - if (ShouldValidate(link)) - link.People = await _people.CreateIfNotExists(link.People, true); - - if (resource.ExternalIDs != null) - foreach (MetadataID link in resource.ExternalIDs) - if (ShouldValidate(link)) - link.Provider = await _providers.CreateIfNotExists(link.Provider, true); + resource.Studio = await _studios.CreateIfNotExists(resource.Studio, true); + resource.Genres = await resource.Genres + .SelectAsync(x => _genres.CreateIfNotExists(x, true)) + .ToListAsync(); + await resource.ExternalIDs.ForEachAsync(async id => + id.Provider = await _providers.CreateIfNotExists(id.Provider, true)); + await resource.People.ForEachAsync(async role => + role.People = await _people.CreateIfNotExists(role.People, true)); } - + + protected override async Task EditRelations(Show resource, Show changed, bool resetOld) + { + if (changed.Aliases != null || resetOld) + resource.Aliases = changed.Aliases; + + 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(); + resource.People = changed.People; + + if (changed.ExternalIDs != null || resetOld) + await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); + resource.ExternalIDs = changed.ExternalIDs; + await base.EditRelations(resource, changed, resetOld); + } + public async Task AddShowLink(int showID, int? libraryID, int? collectionID) { Show show = await Get(showID); diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 623e5090..f8afc757 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -42,10 +42,6 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; - - // Using Dotnet-EF change discovery service to remove references to this studio on shows. - foreach (Show show in obj.Shows) - show.StudioID = null; await _database.SaveChangesAsync(); } } diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 26b47d47..b4c5b00e 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -97,14 +97,7 @@ namespace Kyoo.Controllers await _database.SaveChangesAsync($"Trying to insert a duplicated track (slug {obj.Slug} already exists)."); return obj; } - - - - protected override Task Validate(Track resource) - { - return Task.CompletedTask; - } - + public override async Task Delete(Track obj) { if (obj == null) diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.Designer.cs new file mode 100644 index 00000000..a53b427f --- /dev/null +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.Designer.cs @@ -0,0 +1,778 @@ +// +using System; +using Kyoo; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Kyoo.Models.DatabaseMigrations.Internal +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20210316095337_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) + .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) + .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("Kyoo.Models.Collection", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Collections"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AbsoluteNumber") + .HasColumnType("integer"); + + b.Property("EpisodeNumber") + .HasColumnType("integer"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("ReleaseDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Runtime") + .HasColumnType("integer"); + + b.Property("SeasonID") + .HasColumnType("integer"); + + b.Property("SeasonNumber") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Thumb") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("SeasonID"); + + b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") + .IsUnique(); + + b.ToTable("Episodes"); + }); + + modelBuilder.Entity("Kyoo.Models.Genre", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Genres"); + }); + + modelBuilder.Entity("Kyoo.Models.Library", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Paths") + .HasColumnType("text[]"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Libraries"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.MetadataID", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("DataID") + .HasColumnType("text"); + + b.Property("EpisodeID") + .HasColumnType("integer"); + + b.Property("Link") + .HasColumnType("text"); + + b.Property("PeopleID") + .HasColumnType("integer"); + + b.Property("ProviderID") + .HasColumnType("integer"); + + b.Property("SeasonID") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("EpisodeID"); + + b.HasIndex("PeopleID"); + + b.HasIndex("ProviderID"); + + b.HasIndex("SeasonID"); + + b.HasIndex("ShowID"); + + b.ToTable("MetadataIds"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("People"); + }); + + modelBuilder.Entity("Kyoo.Models.PeopleRole", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("PeopleID") + .HasColumnType("integer"); + + b.Property("Role") + .HasColumnType("text"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("PeopleID"); + + b.HasIndex("ShowID"); + + b.ToTable("PeopleRoles"); + }); + + modelBuilder.Entity("Kyoo.Models.ProviderID", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Logo") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Providers"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("SeasonNumber") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("ShowID", "SeasonNumber") + .IsUnique(); + + b.ToTable("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Aliases") + .HasColumnType("text[]"); + + b.Property("Backdrop") + .HasColumnType("text"); + + b.Property("EndYear") + .HasColumnType("integer"); + + b.Property("IsMovie") + .HasColumnType("boolean"); + + b.Property("Logo") + .HasColumnType("text"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartYear") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StudioID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("TrailerUrl") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("StudioID"); + + b.ToTable("Shows"); + }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Studios"); + }); + + modelBuilder.Entity("Kyoo.Models.Track", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Codec") + .HasColumnType("text"); + + b.Property("EpisodeID") + .HasColumnType("integer"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("IsForced") + .HasColumnType("boolean"); + + b.Property("Language") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("EpisodeID"); + + b.ToTable("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.HasOne("Kyoo.Models.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonID"); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("Episodes") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Collection", "First") + .WithMany("ShowLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Second") + .WithMany("CollectionLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("CollectionLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("ProviderLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.ProviderID", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("ShowLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Show", "First") + .WithMany("GenreLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Genre", "Second") + .WithMany("ShowLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.MetadataID", b => + { + b.HasOne("Kyoo.Models.Episode", "Episode") + .WithMany("ExternalIDs") + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.People", "People") + .WithMany("ExternalIDs") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.ProviderID", "Provider") + .WithMany("MetadataLinks") + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", "Season") + .WithMany("ExternalIDs") + .HasForeignKey("SeasonID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("ExternalIDs") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("People"); + + b.Navigation("Provider"); + + b.Navigation("Season"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.PeopleRole", b => + { + b.HasOne("Kyoo.Models.People", "People") + .WithMany("Roles") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("People") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("People"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.HasOne("Kyoo.Models.Studio", "Studio") + .WithMany("Shows") + .HasForeignKey("StudioID"); + + b.Navigation("Studio"); + }); + + modelBuilder.Entity("Kyoo.Models.Track", b => + { + b.HasOne("Kyoo.Models.Episode", "Episode") + .WithMany("Tracks") + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("Kyoo.Models.Collection", b => + { + b.Navigation("LibraryLinks"); + + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.Genre", b => + { + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Library", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("ProviderLinks"); + + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Kyoo.Models.ProviderID", b => + { + b.Navigation("LibraryLinks"); + + b.Navigation("MetadataLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + + b.Navigation("GenreLinks"); + + b.Navigation("LibraryLinks"); + + b.Navigation("People"); + + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Navigation("Shows"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.cs new file mode 100644 index 00000000..4b007b7f --- /dev/null +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.cs @@ -0,0 +1,604 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Kyoo.Models.DatabaseMigrations.Internal +{ + 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", "finished,airing,planned,unknown") + .Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,font"); + + 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), + Poster = table.Column(type: "text", 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), + Poster = table.Column(type: "text", 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), + Logo = table.Column(type: "text", 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: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Collections_SecondID", + column: x => x.SecondID, + principalTable: "Collections", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Libraries_FirstID", + column: x => x.FirstID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Libraries_FirstID", + column: x => x.FirstID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Providers_SecondID", + column: x => x.SecondID, + 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: "integer", nullable: true), + TrailerUrl = table.Column(type: "text", nullable: true), + StartYear = table.Column(type: "integer", nullable: true), + EndYear = table.Column(type: "integer", nullable: true), + Poster = table.Column(type: "text", nullable: true), + Logo = table.Column(type: "text", nullable: true), + Backdrop = table.Column(type: "text", nullable: true), + IsMovie = table.Column(type: "boolean", nullable: false), + StudioID = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Shows", x => x.ID); + table.ForeignKey( + name: "FK_Shows_Studios_StudioID", + column: x => x.StudioID, + principalTable: "Studios", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Collections_FirstID", + column: x => x.FirstID, + principalTable: "Collections", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Shows_SecondID", + column: x => x.SecondID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Libraries_FirstID", + column: x => x.FirstID, + principalTable: "Libraries", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Shows_SecondID", + column: x => x.SecondID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Link", + columns: table => new + { + FirstID = table.Column(type: "integer", nullable: false), + SecondID = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.ForeignKey( + name: "FK_Link_Genres_SecondID", + column: x => x.SecondID, + principalTable: "Genres", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Link_Shows_FirstID", + column: x => x.FirstID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PeopleRoles", + columns: table => new + { + ID = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + PeopleID = table.Column(type: "integer", nullable: false), + ShowID = table.Column(type: "integer", nullable: false), + Role = table.Column(type: "text", nullable: true), + Type = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PeopleRoles", x => x.ID); + table.ForeignKey( + name: "FK_PeopleRoles_People_PeopleID", + column: x => x.PeopleID, + principalTable: "People", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PeopleRoles_Shows_ShowID", + column: x => x.ShowID, + 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), + ShowID = table.Column(type: "integer", nullable: false), + SeasonNumber = table.Column(type: "integer", nullable: false), + Title = table.Column(type: "text", nullable: true), + Overview = table.Column(type: "text", nullable: true), + Year = table.Column(type: "integer", nullable: true), + Poster = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Seasons", x => x.ID); + table.ForeignKey( + name: "FK_Seasons_Shows_ShowID", + column: x => x.ShowID, + 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), + ShowID = table.Column(type: "integer", nullable: false), + SeasonID = table.Column(type: "integer", nullable: true), + SeasonNumber = table.Column(type: "integer", nullable: false), + EpisodeNumber = table.Column(type: "integer", nullable: false), + AbsoluteNumber = table.Column(type: "integer", nullable: false), + Path = table.Column(type: "text", nullable: true), + Thumb = table.Column(type: "text", nullable: true), + Title = table.Column(type: "text", nullable: true), + Overview = table.Column(type: "text", nullable: true), + ReleaseDate = table.Column(type: "timestamp without time zone", nullable: true), + Runtime = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Episodes", x => x.ID); + table.ForeignKey( + name: "FK_Episodes_Seasons_SeasonID", + column: x => x.SeasonID, + principalTable: "Seasons", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Episodes_Shows_ShowID", + column: x => x.ShowID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MetadataIds", + columns: table => new + { + ID = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ProviderID = table.Column(type: "integer", nullable: false), + ShowID = table.Column(type: "integer", nullable: true), + EpisodeID = table.Column(type: "integer", nullable: true), + SeasonID = table.Column(type: "integer", nullable: true), + PeopleID = table.Column(type: "integer", nullable: true), + DataID = table.Column(type: "text", nullable: true), + Link = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MetadataIds", x => x.ID); + table.ForeignKey( + name: "FK_MetadataIds_Episodes_EpisodeID", + column: x => x.EpisodeID, + principalTable: "Episodes", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MetadataIds_People_PeopleID", + column: x => x.PeopleID, + principalTable: "People", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MetadataIds_Providers_ProviderID", + column: x => x.ProviderID, + principalTable: "Providers", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MetadataIds_Seasons_SeasonID", + column: x => x.SeasonID, + principalTable: "Seasons", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MetadataIds_Shows_ShowID", + column: x => x.ShowID, + principalTable: "Shows", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Tracks", + columns: table => new + { + ID = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EpisodeID = table.Column(type: "integer", nullable: false), + IsDefault = table.Column(type: "boolean", nullable: false), + IsForced = table.Column(type: "boolean", nullable: false), + IsExternal = table.Column(type: "boolean", nullable: false), + Title = table.Column(type: "text", nullable: true), + Language = table.Column(type: "text", nullable: true), + Codec = table.Column(type: "text", nullable: true), + Path = table.Column(type: "text", nullable: true), + Type = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tracks", x => x.ID); + table.ForeignKey( + name: "FK_Tracks_Episodes_EpisodeID", + column: x => x.EpisodeID, + principalTable: "Episodes", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Collections_Slug", + table: "Collections", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Episodes_SeasonID", + table: "Episodes", + column: "SeasonID"); + + migrationBuilder.CreateIndex( + name: "IX_Episodes_ShowID_SeasonNumber_EpisodeNumber_AbsoluteNumber", + table: "Episodes", + columns: new[] { "ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber" }, + 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_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_Link_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_Link_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_Link_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_Link_SecondID", + table: "Link", + column: "SecondID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_EpisodeID", + table: "MetadataIds", + column: "EpisodeID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_PeopleID", + table: "MetadataIds", + column: "PeopleID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_ProviderID", + table: "MetadataIds", + column: "ProviderID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_SeasonID", + table: "MetadataIds", + column: "SeasonID"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataIds_ShowID", + table: "MetadataIds", + column: "ShowID"); + + migrationBuilder.CreateIndex( + name: "IX_People_Slug", + table: "People", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PeopleRoles_PeopleID", + table: "PeopleRoles", + column: "PeopleID"); + + migrationBuilder.CreateIndex( + name: "IX_PeopleRoles_ShowID", + table: "PeopleRoles", + column: "ShowID"); + + migrationBuilder.CreateIndex( + name: "IX_Providers_Slug", + table: "Providers", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Seasons_ShowID_SeasonNumber", + table: "Seasons", + columns: new[] { "ShowID", "SeasonNumber" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Shows_Slug", + table: "Shows", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Shows_StudioID", + table: "Shows", + column: "StudioID"); + + migrationBuilder.CreateIndex( + name: "IX_Studios_Slug", + table: "Studios", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Tracks_EpisodeID", + table: "Tracks", + column: "EpisodeID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "Link"); + + migrationBuilder.DropTable( + name: "MetadataIds"); + + migrationBuilder.DropTable( + name: "PeopleRoles"); + + migrationBuilder.DropTable( + name: "Tracks"); + + migrationBuilder.DropTable( + name: "Collections"); + + migrationBuilder.DropTable( + name: "Libraries"); + + migrationBuilder.DropTable( + name: "Genres"); + + migrationBuilder.DropTable( + name: "Providers"); + + migrationBuilder.DropTable( + name: "People"); + + migrationBuilder.DropTable( + name: "Episodes"); + + migrationBuilder.DropTable( + name: "Seasons"); + + migrationBuilder.DropTable( + name: "Shows"); + + migrationBuilder.DropTable( + name: "Studios"); + } + } +} diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs new file mode 100644 index 00000000..50070827 --- /dev/null +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -0,0 +1,776 @@ +// +using System; +using Kyoo; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Kyoo.Models.DatabaseMigrations.Internal +{ + [DbContext(typeof(DatabaseContext))] + partial class DatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasPostgresEnum(null, "item_type", new[] { "show", "movie", "collection" }) + .HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" }) + .HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "font" }) + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("Kyoo.Models.Collection", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Collections"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AbsoluteNumber") + .HasColumnType("integer"); + + b.Property("EpisodeNumber") + .HasColumnType("integer"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("ReleaseDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Runtime") + .HasColumnType("integer"); + + b.Property("SeasonID") + .HasColumnType("integer"); + + b.Property("SeasonNumber") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Thumb") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("SeasonID"); + + b.HasIndex("ShowID", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") + .IsUnique(); + + b.ToTable("Episodes"); + }); + + modelBuilder.Entity("Kyoo.Models.Genre", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Genres"); + }); + + modelBuilder.Entity("Kyoo.Models.Library", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Paths") + .HasColumnType("text[]"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Libraries"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.Property("FirstID") + .HasColumnType("integer"); + + b.Property("SecondID") + .HasColumnType("integer"); + + b.HasKey("FirstID", "SecondID"); + + b.HasIndex("SecondID"); + + b.ToTable("Link"); + }); + + modelBuilder.Entity("Kyoo.Models.MetadataID", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("DataID") + .HasColumnType("text"); + + b.Property("EpisodeID") + .HasColumnType("integer"); + + b.Property("Link") + .HasColumnType("text"); + + b.Property("PeopleID") + .HasColumnType("integer"); + + b.Property("ProviderID") + .HasColumnType("integer"); + + b.Property("SeasonID") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("EpisodeID"); + + b.HasIndex("PeopleID"); + + b.HasIndex("ProviderID"); + + b.HasIndex("SeasonID"); + + b.HasIndex("ShowID"); + + b.ToTable("MetadataIds"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("People"); + }); + + modelBuilder.Entity("Kyoo.Models.PeopleRole", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("PeopleID") + .HasColumnType("integer"); + + b.Property("Role") + .HasColumnType("text"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("PeopleID"); + + b.HasIndex("ShowID"); + + b.ToTable("PeopleRoles"); + }); + + modelBuilder.Entity("Kyoo.Models.ProviderID", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Logo") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Providers"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("SeasonNumber") + .HasColumnType("integer"); + + b.Property("ShowID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("ShowID", "SeasonNumber") + .IsUnique(); + + b.ToTable("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Aliases") + .HasColumnType("text[]"); + + b.Property("Backdrop") + .HasColumnType("text"); + + b.Property("EndYear") + .HasColumnType("integer"); + + b.Property("IsMovie") + .HasColumnType("boolean"); + + b.Property("Logo") + .HasColumnType("text"); + + b.Property("Overview") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Poster") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartYear") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StudioID") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("TrailerUrl") + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("StudioID"); + + b.ToTable("Shows"); + }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ID"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Studios"); + }); + + modelBuilder.Entity("Kyoo.Models.Track", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Codec") + .HasColumnType("text"); + + b.Property("EpisodeID") + .HasColumnType("integer"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsExternal") + .HasColumnType("boolean"); + + b.Property("IsForced") + .HasColumnType("boolean"); + + b.Property("Language") + .HasColumnType("text"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("ID"); + + b.HasIndex("EpisodeID"); + + b.ToTable("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.HasOne("Kyoo.Models.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonID"); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("Episodes") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Collection", "First") + .WithMany("ShowLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Second") + .WithMany("CollectionLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("CollectionLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Collection", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("ProviderLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.ProviderID", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Library", "First") + .WithMany("ShowLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Second") + .WithMany("LibraryLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.Link", b => + { + b.HasOne("Kyoo.Models.Show", "First") + .WithMany("GenreLinks") + .HasForeignKey("FirstID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Genre", "Second") + .WithMany("ShowLinks") + .HasForeignKey("SecondID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("First"); + + b.Navigation("Second"); + }); + + modelBuilder.Entity("Kyoo.Models.MetadataID", b => + { + b.HasOne("Kyoo.Models.Episode", "Episode") + .WithMany("ExternalIDs") + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.People", "People") + .WithMany("ExternalIDs") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.ProviderID", "Provider") + .WithMany("MetadataLinks") + .HasForeignKey("ProviderID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Season", "Season") + .WithMany("ExternalIDs") + .HasForeignKey("SeasonID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("ExternalIDs") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("People"); + + b.Navigation("Provider"); + + b.Navigation("Season"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.PeopleRole", b => + { + b.HasOne("Kyoo.Models.People", "People") + .WithMany("Roles") + .HasForeignKey("PeopleID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("People") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("People"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.HasOne("Kyoo.Models.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.HasOne("Kyoo.Models.Studio", "Studio") + .WithMany("Shows") + .HasForeignKey("StudioID"); + + b.Navigation("Studio"); + }); + + modelBuilder.Entity("Kyoo.Models.Track", b => + { + b.HasOne("Kyoo.Models.Episode", "Episode") + .WithMany("Tracks") + .HasForeignKey("EpisodeID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Episode"); + }); + + modelBuilder.Entity("Kyoo.Models.Collection", b => + { + b.Navigation("LibraryLinks"); + + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Episode", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Tracks"); + }); + + modelBuilder.Entity("Kyoo.Models.Genre", b => + { + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Library", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("ProviderLinks"); + + b.Navigation("ShowLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.People", b => + { + b.Navigation("ExternalIDs"); + + b.Navigation("Roles"); + }); + + modelBuilder.Entity("Kyoo.Models.ProviderID", b => + { + b.Navigation("LibraryLinks"); + + b.Navigation("MetadataLinks"); + }); + + modelBuilder.Entity("Kyoo.Models.Season", b => + { + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + }); + + modelBuilder.Entity("Kyoo.Models.Show", b => + { + b.Navigation("CollectionLinks"); + + b.Navigation("Episodes"); + + b.Navigation("ExternalIDs"); + + b.Navigation("GenreLinks"); + + b.Navigation("LibraryLinks"); + + b.Navigation("People"); + + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("Kyoo.Models.Studio", b => + { + b.Navigation("Shows"); + }); +#pragma warning restore 612, 618 + } + } +} From 4b56ec4114067e0409c38919fd082bea9a8fd875 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 16 Mar 2021 23:20:56 +0100 Subject: [PATCH 42/54] Fixing show's repository --- Kyoo.Common/Models/Link.cs | 13 ++++++- Kyoo.CommonAPI/LocalRepository.cs | 3 +- .../Repositories/ShowRepository.cs | 39 ++++++++++++------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Kyoo.Common/Models/Link.cs b/Kyoo.Common/Models/Link.cs index b78d3508..cacc7c64 100644 --- a/Kyoo.Common/Models/Link.cs +++ b/Kyoo.Common/Models/Link.cs @@ -28,6 +28,13 @@ namespace Kyoo.Models return new(first, second); } + public static Link UCreate(T first, T2 second) + where T : class, IResource + where T2 : class, IResource + { + return new(first, second, true); + } + public static Expression> PrimaryKey { get @@ -47,13 +54,15 @@ namespace Kyoo.Models public Link() {} - public Link(T1 first, T2 second) + public Link(T1 first, T2 second, bool privateItems = false) : base(first, second) { + if (privateItems) + return; First = first; Second = second; } - + public new static Expression, object>> PrimaryKey { get diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 874272aa..660ef8af 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -167,9 +167,8 @@ namespace Kyoo.Controllers if (resetOld) Utility.Nullify(old); - await EditRelations(old, edited, resetOld); Utility.Complete(old, edited, x => x.GetCustomAttribute() == null); - await Validate(old); + await EditRelations(old, edited, resetOld); await Database.SaveChangesAsync(); return old; } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index a820a02e..53b79936 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -100,32 +100,45 @@ namespace Kyoo.Controllers { await base.Validate(resource); resource.Studio = await _studios.CreateIfNotExists(resource.Studio, true); - resource.Genres = await resource.Genres - .SelectAsync(x => _genres.CreateIfNotExists(x, true)) + resource.GenreLinks = await resource.Genres + .SelectAsync(async x => Link.UCreate(resource, await _genres.CreateIfNotExists(x, true))) .ToListAsync(); - await resource.ExternalIDs.ForEachAsync(async id => - id.Provider = await _providers.CreateIfNotExists(id.Provider, true)); + await resource.ExternalIDs.ForEachAsync(async id => + { + id.ProviderID = (await _providers.CreateIfNotExists(id.Provider, true)).ID; + id.Provider = null; + }); await resource.People.ForEachAsync(async role => - role.People = await _people.CreateIfNotExists(role.People, true)); + { + role.PeopleID = (await _people.CreateIfNotExists(role.People, true)).ID; + role.People = null; + }); } protected override async Task EditRelations(Show resource, Show changed, bool resetOld) { + await Validate(changed); + if (changed.Aliases != null || resetOld) resource.Aliases = changed.Aliases; - + if (changed.Genres != null || resetOld) - await Database.Entry(resource).Collection(x => x.Genres).LoadAsync(); - resource.Genres = changed.Genres; - + { + await Database.Entry(resource).Collection(x => x.GenreLinks).LoadAsync(); + resource.GenreLinks = changed.GenreLinks; + } + if (changed.People != null || resetOld) + { await Database.Entry(resource).Collection(x => x.People).LoadAsync(); - resource.People = changed.People; - + resource.People = changed.People; + } + if (changed.ExternalIDs != null || resetOld) + { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); - resource.ExternalIDs = changed.ExternalIDs; - await base.EditRelations(resource, changed, resetOld); + resource.ExternalIDs = changed.ExternalIDs; + } } public async Task AddShowLink(int showID, int? libraryID, int? collectionID) From cfc3e4b8656060e185b3a27d90cd3db459f2f3ed Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 17 Mar 2021 00:03:06 +0100 Subject: [PATCH 43/54] Fixing others's repository edit --- .../Repositories/EpisodeRepository.cs | 10 ++++---- .../Repositories/LibraryRepository.cs | 6 +++-- .../Repositories/PeopleRepository.cs | 23 +++++++++++++++---- .../Repositories/SeasonRepository.cs | 11 ++++++--- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index a68161c2..35602545 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -168,9 +168,7 @@ namespace Kyoo.Controllers { if (resource.ShowID <= 0) throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {resource.ShowID})."); - - await base.EditRelations(resource, changed, resetOld); - + if (changed.Tracks != null || resetOld) { ICollection oldTracks = await _tracks.GetAll(x => x.EpisodeID == resource.ID); @@ -190,7 +188,11 @@ namespace Kyoo.Controllers if (changed.ExternalIDs != null || resetOld) { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); - resource.ExternalIDs = changed.ExternalIDs; + resource.ExternalIDs = changed.ExternalIDs?.Select(x => + { + x.Provider = null; + return x; + }).ToList(); } } diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 70e6d77e..b4d725c1 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -79,9 +79,11 @@ namespace Kyoo.Controllers throw new ArgumentException("The library should have a least one path."); if (changed.Providers != null || resetOld) + { + await Validate(changed); await Database.Entry(resource).Collection(x => x.Providers).LoadAsync(); - resource.Providers = changed.Providers; - await base.EditRelations(resource, changed, resetOld); + resource.Providers = changed.Providers; + } } public override async Task Delete(Library obj) diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 58d0c777..04570182 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -71,20 +71,33 @@ namespace Kyoo.Controllers protected override async Task Validate(People resource) { await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async id => - id.Provider = await _providers.CreateIfNotExists(id.Provider, true)); + await resource.ExternalIDs.ForEachAsync(async id => + { + id.ProviderID = (await _providers.CreateIfNotExists(id.Provider, true)).ID; + id.Provider = null; + + }); await resource.Roles.ForEachAsync(async role => - role.Show = await _shows.Value.CreateIfNotExists(role.Show, true)); + { + role.ShowID = (await _shows.Value.CreateIfNotExists(role.Show, true)).ID; + role.Show = null; + }); } protected override async Task EditRelations(People resource, People changed, bool resetOld) { if (changed.Roles != null || resetOld) + { await Database.Entry(resource).Collection(x => x.Roles).LoadAsync(); - resource.Roles = changed.Roles; + resource.Roles = changed.Roles; + } + if (changed.ExternalIDs != null || resetOld) + { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); - resource.ExternalIDs = changed.ExternalIDs; + resource.ExternalIDs = changed.ExternalIDs; + + } await base.EditRelations(resource, changed, resetOld); } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index ba076c02..d1bc271f 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -138,15 +138,20 @@ namespace Kyoo.Controllers throw new InvalidOperationException($"Can't store a season not related to any show (showID: {resource.ShowID})."); await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async id => - id.Provider = await _providers.CreateIfNotExists(id.Provider, true)); + await resource.ExternalIDs.ForEachAsync(async id => + { + id.ProviderID = (await _providers.CreateIfNotExists(id.Provider, true)).ID; + id.Provider = null; + }); } protected override async Task EditRelations(Season resource, Season changed, bool resetOld) { if (changed.ExternalIDs != null || resetOld) + { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); - resource.ExternalIDs = changed.ExternalIDs; + resource.ExternalIDs = changed.ExternalIDs; + } await base.EditRelations(resource, changed, resetOld); } From d0715055f176f3f67ca2f43f66c9eaca2ec2ec33 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 17 Mar 2021 00:39:41 +0100 Subject: [PATCH 44/54] Fixing tracks edits, still need to fix slugs --- Kyoo.Common/Models/Resources/Track.cs | 1 + .../Repositories/EpisodeRepository.cs | 18 +++++++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 7895bba6..6d9f3b81 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -94,6 +94,7 @@ namespace Kyoo.Models { get { + // TODO other type of tracks should still have slugs. The slug should never be an ID. Maybe a und-number format. if (Type != StreamType.Subtitle) return null; diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 35602545..162d03e3 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -172,17 +172,13 @@ namespace Kyoo.Controllers if (changed.Tracks != null || resetOld) { ICollection oldTracks = await _tracks.GetAll(x => x.EpisodeID == resource.ID); - resource.Tracks = await changed.Tracks.SelectAsync(async track => - { - Track oldValue = oldTracks?.FirstOrDefault(x => Utility.ResourceEquals(track, x)); - if (oldValue == null) - return await _tracks.CreateIfNotExists(track, true); - oldTracks.Remove(oldValue); - return oldValue; - }) - .ToListAsync(); - foreach (Track x in oldTracks) - await _tracks.Delete(x); + await _tracks.DeleteRange(oldTracks); + resource.Tracks = await changed.Tracks.SelectAsync(x => + { + x.Episode = resource; + x.EpisodeID = resource.ID; + return _tracks.Create(x); + }).ToListAsync(); } if (changed.ExternalIDs != null || resetOld) From 457dcd0db2cc9454371a00f83786432820babfdf Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 17 Mar 2021 19:56:02 +0100 Subject: [PATCH 45/54] Removing usless calls, cleaning track's slugs, adding a track api, cleaning show & episode repository --- Kyoo.Common/Controllers/IRepository.cs | 10 +- Kyoo.Common/ExpressionRewrite.cs | 115 ------------------ Kyoo.Common/Models/Resources/Track.cs | 20 +-- Kyoo.Common/Utility.cs | 88 ++++++-------- Kyoo.CommonAPI/ApiHelper.cs | 10 +- .../Repositories/EpisodeRepository.cs | 45 ++++--- .../Repositories/ShowRepository.cs | 3 +- Kyoo/Kyoo.csproj | 2 +- ....cs => 20210317180448_Initial.Designer.cs} | 5 +- ...7_Initial.cs => 20210317180448_Initial.cs} | 1 + .../Internal/DatabaseContextModelSnapshot.cs | 3 + Kyoo/Views/API/TrackApi.cs | 56 +++++++++ 12 files changed, 145 insertions(+), 213 deletions(-) delete mode 100644 Kyoo.Common/ExpressionRewrite.cs rename Kyoo/Models/DatabaseMigrations/Internal/{20210316095337_Initial.Designer.cs => 20210317180448_Initial.Designer.cs} (99%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210316095337_Initial.cs => 20210317180448_Initial.cs} (99%) create mode 100644 Kyoo/Views/API/TrackApi.cs diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 4e8b2440..b0b42cc6 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -30,7 +30,7 @@ namespace Kyoo.Controllers public Sort(Expression> key, bool descendant = false) { - Key = ExpressionRewrite.Rewrite>(key); + Key = key; Descendant = descendant; if (!Utility.IsPropertyExpression(Key)) @@ -54,8 +54,7 @@ namespace Kyoo.Controllers Key = property.Type.IsValueType ? Expression.Lambda>(Expression.Convert(property, typeof(object)), param) : Expression.Lambda>(property, param); - Key = ExpressionRewrite.Rewrite>(Key); - + Descendant = order switch { "desc" => true, @@ -64,11 +63,6 @@ namespace Kyoo.Controllers _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") }; } - - public Sort To() - { - return new(Key.Convert>(), Descendant); - } } public interface IRepository : IDisposable, IAsyncDisposable where T : class, IResource diff --git a/Kyoo.Common/ExpressionRewrite.cs b/Kyoo.Common/ExpressionRewrite.cs deleted file mode 100644 index 3bb5bc5c..00000000 --- a/Kyoo.Common/ExpressionRewrite.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; - -namespace Kyoo -{ - public class ExpressionRewriteAttribute : Attribute - { - public string Link { get; } - public string Inner { get; } - - public ExpressionRewriteAttribute(string link, string inner = null) - { - Link = link; - Inner = inner; - } - } - - public class ExpressionRewrite : ExpressionVisitor - { - private string _inner; - private readonly List<(string inner, ParameterExpression param, ParameterExpression newParam)> _innerRewrites; - - private ExpressionRewrite() - { - _innerRewrites = new List<(string, ParameterExpression, ParameterExpression)>(); - } - - public static Expression Rewrite(Expression expression) - { - return new ExpressionRewrite().Visit(expression); - } - - public static Expression Rewrite(Expression expression) where T : Delegate - { - return (Expression)new ExpressionRewrite().Visit(expression); - } - - protected override Expression VisitMember(MemberExpression node) - { - (string inner, _, ParameterExpression p) = _innerRewrites.FirstOrDefault(x => x.param == node.Expression); - if (inner != null) - { - Expression param = inner.Split('.').Aggregate(p, Expression.Property); - node = Expression.Property(param, node.Member.Name); - } - - // Can't use node.Member directly because we want to support attribute override - MemberInfo member = node.Expression!.Type.GetProperty(node.Member.Name) ?? node.Member; - ExpressionRewriteAttribute attr = member!.GetCustomAttribute(); - if (attr == null) - return base.VisitMember(node); - - Expression property = attr.Link.Split('.').Aggregate(node.Expression, Expression.Property); - if (property is MemberExpression expr) - Visit(expr.Expression); - _inner = attr.Inner; - return property!; - } - - protected override Expression VisitLambda(Expression node) - { - (_, ParameterExpression oldParam, ParameterExpression param) = _innerRewrites - .FirstOrDefault(x => node.Parameters.Any(y => y == x.param)); - if (param == null) - return base.VisitLambda(node); - - ParameterExpression[] newParams = node.Parameters.Where(x => x != oldParam).Append(param).ToArray(); - return Expression.Lambda(Visit(node.Body)!, newParams); - } - - protected override Expression VisitMethodCall(MethodCallExpression node) - { - int count = node.Arguments.Count; - if (node.Object != null) - count++; - if (count != 2) - return base.VisitMethodCall(node); - - Expression instance = node.Object ?? node.Arguments.First(); - Expression argument = node.Object != null - ? node.Arguments.First() - : node.Arguments[1]; - - Type oldType = instance.Type; - instance = Visit(instance); - if (instance!.Type == oldType) - return base.VisitMethodCall(node); - - if (_inner != null && argument is LambdaExpression lambda) - { - // TODO this type handler will usually work with IEnumerable & others but won't work with everything. - Type type = oldType.GetGenericArguments().First(); - ParameterExpression oldParam = lambda.Parameters.FirstOrDefault(x => x.Type == type); - if (oldParam != null) - { - Type newType = instance.Type.GetGenericArguments().First(); - ParameterExpression newParam = Expression.Parameter(newType, oldParam.Name); - _innerRewrites.Add((_inner, oldParam, newParam)); - } - } - argument = Visit(argument); - - // TODO this method handler may not work for some methods (ex: method taking a Fun<> method won't have good generic arguments) - MethodInfo method = node.Method.IsGenericMethod - ? node.Method.GetGenericMethodDefinition().MakeGenericMethod(instance.Type.GetGenericArguments()) - : node.Method; - return node.Object != null - ? Expression.Call(instance, method!, argument) - : Expression.Call(null, method!, instance, argument!); - } - } -} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 6d9f3b81..ec4d2919 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -58,6 +58,7 @@ namespace Kyoo.Models { public int ID { get; set; } [SerializeIgnore] public int EpisodeID { get; set; } + public int TrackIndex { get; set; } public bool IsDefault { get => isDefault; @@ -94,13 +95,17 @@ namespace Kyoo.Models { get { - // TODO other type of tracks should still have slugs. The slug should never be an ID. Maybe a und-number format. - if (Type != StreamType.Subtitle) - return null; - - string slug = string.IsNullOrEmpty(Language) - ? ID.ToString() - : $"{Episode.Slug}.{Language}{(IsForced ? "-forced" : "")}"; + string type = Type switch + { + StreamType.Subtitle => "", + StreamType.Video => "video.", + StreamType.Audio => "audio.", + StreamType.Font => "font.", + _ => "" + }; + string slug = $"{Episode.Slug}.{type}{Language}{(TrackIndex != 0 ? TrackIndex : "")}"; + if (IsForced) + slug += "-forced"; switch (Codec) { case "ass": @@ -144,6 +149,7 @@ namespace Kyoo.Models return mkvLanguage switch { "fre" => "fra", + null => "und", _ => mkvLanguage }; } diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index f672d598..0ee0c881 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -256,6 +256,42 @@ namespace Kyoo return types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); } + public static IEnumerable Map([CanBeNull] this IEnumerable self, + [NotNull] Func mapper) + { + if (self == null) + yield break; + if (mapper == null) + throw new ArgumentNullException(nameof(mapper)); + + using IEnumerator enumerator = self.GetEnumerator(); + int index = 0; + + while (enumerator.MoveNext()) + { + yield return mapper(enumerator.Current, index); + index++; + } + } + + public static async IAsyncEnumerable MapAsync([CanBeNull] this IEnumerable self, + [NotNull] Func> mapper) + { + if (self == null) + yield break; + if (mapper == null) + throw new ArgumentNullException(nameof(mapper)); + + using IEnumerator enumerator = self.GetEnumerator(); + int index = 0; + + while (enumerator.MoveNext()) + { + yield return await mapper(enumerator.Current, index); + index++; + } + } + public static async IAsyncEnumerable SelectAsync([CanBeNull] this IEnumerable self, [NotNull] Func> mapper) { @@ -584,57 +620,5 @@ namespace Kyoo return true; return firstID == secondID; } - - public static Expression Convert([CanBeNull] this Expression expr) - where T : Delegate - { - Expression e = expr switch - { - null => null, - LambdaExpression lambda => new ExpressionConverter(lambda).VisitAndConvert(), - _ => throw new ArgumentException("Can't convert a non lambda.") - }; - - return ExpressionRewrite.Rewrite(e); - } - - private class ExpressionConverter : ExpressionVisitor - where TTo : Delegate - { - private readonly LambdaExpression _expression; - private readonly ParameterExpression[] _newParams; - - internal ExpressionConverter(LambdaExpression expression) - { - _expression = expression; - - Type[] paramTypes = typeof(TTo).GetGenericArguments()[..^1]; - if (paramTypes.Length != _expression.Parameters.Count) - throw new ArgumentException("Parameter count from internal and external lambda are not matched."); - - _newParams = new ParameterExpression[paramTypes.Length]; - for (int i = 0; i < paramTypes.Length; i++) - { - if (_expression.Parameters[i].Type == paramTypes[i]) - _newParams[i] = _expression.Parameters[i]; - else - _newParams[i] = Expression.Parameter(paramTypes[i], _expression.Parameters[i].Name); - } - } - - internal Expression VisitAndConvert() - { - Type returnType = _expression.Type.GetGenericArguments().Last(); - Expression body = _expression.ReturnType == returnType - ? Visit(_expression.Body) - : Expression.Convert(Visit(_expression.Body)!, returnType); - return Expression.Lambda(body!, _newParams); - } - - protected override Expression VisitParameter(ParameterExpression node) - { - return _newParams.FirstOrDefault(x => x.Name == node.Name) ?? node; - } - } } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/ApiHelper.cs b/Kyoo.CommonAPI/ApiHelper.cs index 312ac908..d3d8d951 100644 --- a/Kyoo.CommonAPI/ApiHelper.cs +++ b/Kyoo.CommonAPI/ApiHelper.cs @@ -26,12 +26,7 @@ namespace Kyoo.CommonApi Expression> defaultWhere = null) { if (where == null || where.Count == 0) - { - if (defaultWhere == null) - return null; - Expression body = ExpressionRewrite.Rewrite(defaultWhere.Body); - return Expression.Lambda>(body, defaultWhere.Parameters.First()); - } + return defaultWhere; ParameterExpression param = defaultWhere?.Parameters.First() ?? Expression.Parameter(typeof(T)); Expression expression = defaultWhere?.Body; @@ -97,8 +92,7 @@ namespace Kyoo.CommonApi expression = condition; } - expression = ExpressionRewrite.Rewrite(expression); - return Expression.Lambda>(expression, param); + return Expression.Lambda>(expression!, param); } private static Expression ResourceEqual(Expression parameter, string value, bool notEqual = false) diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 162d03e3..c6b2e307 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -155,13 +155,7 @@ namespace Kyoo.Controllers _database.Entry(obj).State = EntityState.Added; obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); - obj.Tracks = await obj.Tracks.SelectAsync(x => - { - x.Episode = obj; - x.EpisodeID = obj.ID; - return _tracks.CreateIfNotExists(x, true); - }).ToListAsync(); - return obj; + return await ValidateTracks(obj); } protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld) @@ -173,30 +167,41 @@ namespace Kyoo.Controllers { ICollection oldTracks = await _tracks.GetAll(x => x.EpisodeID == resource.ID); await _tracks.DeleteRange(oldTracks); - resource.Tracks = await changed.Tracks.SelectAsync(x => - { - x.Episode = resource; - x.EpisodeID = resource.ID; - return _tracks.Create(x); - }).ToListAsync(); + resource.Tracks = changed.Tracks; + await ValidateTracks(resource); } if (changed.ExternalIDs != null || resetOld) { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); - resource.ExternalIDs = changed.ExternalIDs?.Select(x => - { - x.Provider = null; - return x; - }).ToList(); + resource.ExternalIDs = changed.ExternalIDs; } + + await Validate(resource); } + private async Task ValidateTracks(Episode resource) + { + resource.Tracks = await resource.Tracks.MapAsync((x, i) => + { + x.Episode = resource; + x.TrackIndex = resource.Tracks.Take(i).Count(y => x.Language == y.Language + && x.IsForced == y.IsForced + && x.Codec == y.Codec + && x.Type == y.Type); + return _tracks.CreateIfNotExists(x, true); + }).ToListAsync(); + return resource; + } + protected override async Task Validate(Episode resource) { await base.Validate(resource); - await resource.ExternalIDs.ForEachAsync(async id => - id.Provider = await _providers.CreateIfNotExists(id.Provider, true)); + resource.ExternalIDs = resource.ExternalIDs?.Select(x => + { + x.Provider = null; + return x; + }).ToList(); } public async Task Delete(string showSlug, int seasonNumber, int episodeNumber) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 53b79936..1fdc8c6a 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -99,7 +99,8 @@ namespace Kyoo.Controllers protected override async Task Validate(Show resource) { await base.Validate(resource); - resource.Studio = await _studios.CreateIfNotExists(resource.Studio, true); + if (resource.Studio != null) + resource.Studio = await _studios.CreateIfNotExists(resource.Studio, true); resource.GenreLinks = await resource.Genres .SelectAsync(async x => Link.UCreate(resource, await _genres.CreateIfNotExists(x, true))) .ToListAsync(); diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index e97d430a..97b151f2 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -90,7 +90,7 @@ - + diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.Designer.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.Designer.cs index a53b427f..d62ff33f 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210316095337_Initial")] + [Migration("20210317180448_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -491,6 +491,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Title") .HasColumnType("text"); + b.Property("TrackIndex") + .HasColumnType("integer"); + b.Property("Type") .HasColumnType("integer"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.cs index 4b007b7f..0593207e 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210316095337_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.cs @@ -397,6 +397,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal ID = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), EpisodeID = table.Column(type: "integer", nullable: false), + TrackIndex = table.Column(type: "integer", nullable: false), IsDefault = table.Column(type: "boolean", nullable: false), IsForced = table.Column(type: "boolean", nullable: false), IsExternal = table.Column(type: "boolean", nullable: false), diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 50070827..9889f943 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -489,6 +489,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Title") .HasColumnType("text"); + b.Property("TrackIndex") + .HasColumnType("integer"); + b.Property("Type") .HasColumnType("integer"); diff --git a/Kyoo/Views/API/TrackApi.cs b/Kyoo/Views/API/TrackApi.cs new file mode 100644 index 00000000..689e904b --- /dev/null +++ b/Kyoo/Views/API/TrackApi.cs @@ -0,0 +1,56 @@ +using System.Linq; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Controllers; +using Kyoo.Models; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/track")] + [Route("api/tracks")] + [ApiController] + public class TrackApi : CrudApi + { + private readonly ILibraryManager _libraryManager; + + public TrackApi(ILibraryManager libraryManager, IConfiguration configuration) + : base(libraryManager.TrackRepository, configuration) + { + _libraryManager = libraryManager; + } + + [HttpGet("{id:int}/episode")] + [Authorize(Policy = "Read")] + public async Task> GetEpisode(int id) + { + try + { + return await _libraryManager.GetEpisode(x => x.Tracks.Any(y => y.ID == id)); + } + catch (ItemNotFound) + { + return NotFound(); + } + } + + [HttpGet("{slug}/episode")] + [Authorize(Policy = "Read")] + public async Task> GetEpisode(string slug) + { + try + { + // TODO This won't work with the local repository implementation. + // TODO Implement something like this (a dotnet-ef's QueryCompilationContext): https://stackoverflow.com/questions/62687811/how-can-i-convert-a-custom-function-to-a-sql-expression-for-entity-framework-cor + return await _libraryManager.GetEpisode(x => x.Tracks.Any(y => y.Slug == slug)); + } + catch (ItemNotFound) + { + return NotFound(); + } + } + } +} \ No newline at end of file From aac1975a75212b13981b062ce906e4cdf993d209 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 17 Mar 2021 22:04:28 +0100 Subject: [PATCH 46/54] Adding a delete range and fixing show's editing --- Kyoo.Common/Controllers/IRepository.cs | 1 + Kyoo.CommonAPI/CrudApi.cs | 15 ++++++++++++++ Kyoo.CommonAPI/LocalRepository.cs | 8 +++++++- .../Repositories/EpisodeRepository.cs | 11 +++++----- .../Repositories/PeopleRepository.cs | 11 +++++----- .../Repositories/SeasonRepository.cs | 5 +++-- .../Repositories/ShowRepository.cs | 20 +++++++++++-------- 7 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index b0b42cc6..5beee98b 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -98,6 +98,7 @@ namespace Kyoo.Controllers Task DeleteRange(IEnumerable ids); Task DeleteRange(params string[] slugs) => DeleteRange(slugs.AsEnumerable()); Task DeleteRange(IEnumerable slugs); + Task DeleteRange([NotNull] Expression> where); } public interface IShowRepository : IRepository diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index 4d674cc2..0bbf7733 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -182,5 +182,20 @@ namespace Kyoo.CommonApi return Ok(); } + + [Authorize(Policy = "Write")] + public virtual async Task Delete(Dictionary where) + { + try + { + await _repository.DeleteRange(ApiHelper.ParseWhere(where)); + } + catch (ItemNotFound) + { + return NotFound(); + } + + return Ok(); + } } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 660ef8af..5da8fcc0 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -218,7 +218,7 @@ namespace Kyoo.Controllers } public abstract Task Delete(T obj); - + public virtual async Task DeleteRange(IEnumerable objs) { foreach (T obj in objs) @@ -236,5 +236,11 @@ namespace Kyoo.Controllers foreach (string slug in slugs) await Delete(slug); } + + public async Task DeleteRange(Expression> where) + { + ICollection resources = await GetAll(where); + await DeleteRange(resources); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index c6b2e307..34c689dc 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -165,8 +165,7 @@ namespace Kyoo.Controllers if (changed.Tracks != null || resetOld) { - ICollection oldTracks = await _tracks.GetAll(x => x.EpisodeID == resource.ID); - await _tracks.DeleteRange(oldTracks); + await _tracks.DeleteRange(x => x.EpisodeID == resource.ID); resource.Tracks = changed.Tracks; await ValidateTracks(resource); } @@ -197,11 +196,13 @@ namespace Kyoo.Controllers protected override async Task Validate(Episode resource) { await base.Validate(resource); - resource.ExternalIDs = resource.ExternalIDs?.Select(x => + resource.ExternalIDs = await resource.ExternalIDs.SelectAsync(async x => { - x.Provider = null; + x.Provider = await _providers.CreateIfNotExists(x.Provider, true); + x.ProviderID = x.Provider.ID; + _database.Entry(x.Provider).State = EntityState.Detached; return x; - }).ToList(); + }).ToListAsync(); } public async Task Delete(string showSlug, int seasonNumber, int episodeNumber) diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 04570182..9097bea8 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -73,14 +73,15 @@ namespace Kyoo.Controllers await base.Validate(resource); await resource.ExternalIDs.ForEachAsync(async id => { - id.ProviderID = (await _providers.CreateIfNotExists(id.Provider, true)).ID; - id.Provider = null; - + id.Provider = await _providers.CreateIfNotExists(id.Provider, true); + id.ProviderID = id.Provider.ID; + _database.Entry(id.Provider).State = EntityState.Detached; }); await resource.Roles.ForEachAsync(async role => { - role.ShowID = (await _shows.Value.CreateIfNotExists(role.Show, true)).ID; - role.Show = null; + role.Show = await _shows.Value.CreateIfNotExists(role.Show, true); + role.ShowID = role.Show.ID; + _database.Entry(role.Show).State = EntityState.Detached; }); } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index d1bc271f..29c3e400 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -140,8 +140,9 @@ namespace Kyoo.Controllers await base.Validate(resource); await resource.ExternalIDs.ForEachAsync(async id => { - id.ProviderID = (await _providers.CreateIfNotExists(id.Provider, true)).ID; - id.Provider = null; + id.Provider = await _providers.CreateIfNotExists(id.Provider, true); + id.ProviderID = id.Provider.ID; + _database.Entry(id.Provider).State = EntityState.Detached; }); } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 1fdc8c6a..be04ffc9 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -88,7 +88,6 @@ namespace Kyoo.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - obj.GenreLinks = obj.Genres?.Select(x => Link.Create(obj, x)).ToArray(); obj.GenreLinks.ForEach(x => _database.Entry(x).State = EntityState.Added); obj.People.ForEach(x => _database.Entry(x).State = EntityState.Added); obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); @@ -101,18 +100,23 @@ namespace Kyoo.Controllers await base.Validate(resource); if (resource.Studio != null) resource.Studio = await _studios.CreateIfNotExists(resource.Studio, true); - resource.GenreLinks = await resource.Genres - .SelectAsync(async x => Link.UCreate(resource, await _genres.CreateIfNotExists(x, true))) + resource.Genres = await resource.Genres + .SelectAsync(x => _genres.CreateIfNotExists(x, true)) .ToListAsync(); + resource.GenreLinks = resource.Genres? + .Select(x => Link.UCreate(resource, x)) + .ToList(); await resource.ExternalIDs.ForEachAsync(async id => { - id.ProviderID = (await _providers.CreateIfNotExists(id.Provider, true)).ID; - id.Provider = null; + id.Provider = await _providers.CreateIfNotExists(id.Provider, true); + id.ProviderID = id.Provider.ID; + _database.Entry(id.Provider).State = EntityState.Detached; }); await resource.People.ForEachAsync(async role => { - role.PeopleID = (await _people.CreateIfNotExists(role.People, true)).ID; - role.People = null; + role.People = await _people.CreateIfNotExists(role.People, true); + role.PeopleID = role.People.ID; + _database.Entry(role.People).State = EntityState.Detached; }); } @@ -126,7 +130,7 @@ namespace Kyoo.Controllers if (changed.Genres != null || resetOld) { await Database.Entry(resource).Collection(x => x.GenreLinks).LoadAsync(); - resource.GenreLinks = changed.GenreLinks; + resource.GenreLinks = changed.Genres?.Select(x => Link.UCreate(resource, x)).ToList(); } if (changed.People != null || resetOld) From 2138f0909be524d7844bc4106f228278f81af81c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 18 Mar 2021 00:16:14 +0100 Subject: [PATCH 47/54] Adding a TrackIndex for tracks, support duplicated values & slugs for tracks that are not subtitles --- Kyoo.Common/Models/Resources/Track.cs | 18 ++++------- .../Repositories/EpisodeRepository.cs | 2 +- .../Repositories/TrackRepository.cs | 15 ++++++++-- Kyoo/Models/DatabaseContext.cs | 30 +++++++++++++++++++ ....cs => 20210317220956_Initial.Designer.cs} | 5 ++-- ...8_Initial.cs => 20210317220956_Initial.cs} | 5 ++-- .../Internal/DatabaseContextModelSnapshot.cs | 3 +- Kyoo/Startup.cs | 2 +- Kyoo/Views/API/SubtitleApi.cs | 2 +- 9 files changed, 59 insertions(+), 23 deletions(-) rename Kyoo/Models/DatabaseMigrations/Internal/{20210317180448_Initial.Designer.cs => 20210317220956_Initial.Designer.cs} (99%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210317180448_Initial.cs => 20210317220956_Initial.cs} (99%) diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index ec4d2919..544dc40e 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -103,19 +103,13 @@ namespace Kyoo.Models StreamType.Font => "font.", _ => "" }; - string slug = $"{Episode.Slug}.{type}{Language}{(TrackIndex != 0 ? TrackIndex : "")}"; - if (IsForced) - slug += "-forced"; - switch (Codec) + string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty; + string codec = Codec switch { - case "ass": - slug += ".ass"; - break; - case "subrip": - slug += ".srt"; - break; - } - return slug; + "subrip" => ".srt", + {} x => $".{x}" + }; + return $"{Episode.Slug}.{type}{Language}{index}{(IsForced ? "-forced" : "")}{codec}"; } } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 34c689dc..77901701 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -188,7 +188,7 @@ namespace Kyoo.Controllers && x.IsForced == y.IsForced && x.Codec == y.Codec && x.Type == y.Type); - return _tracks.CreateIfNotExists(x, true); + return _tracks.Create(x); }).ToListAsync(); return resource; } diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index b4c5b00e..d4e04376 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -4,6 +4,7 @@ using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers @@ -12,7 +13,7 @@ namespace Kyoo.Controllers { private bool _disposed; private readonly DatabaseContext _database; - protected override Expression> DefaultSort => x => x.ID; + protected override Expression> DefaultSort => x => x.TrackIndex; public TrackRepository(DatabaseContext database) : base(database) @@ -44,7 +45,7 @@ namespace Kyoo.Controllers public Task Get(string slug, StreamType type) { Match match = Regex.Match(slug, - @"(?.*)-s(?\d+)e(?\d+)\.(?.{0,3})(?-forced)?(\..*)?"); + @"(?.*)-s(?\d+)e(?\d+)(\.(?\w*))?\.(?.{0,3})(?-forced)?(\..*)?"); if (!match.Success) { @@ -61,6 +62,8 @@ namespace Kyoo.Controllers int episodeNumber = match.Groups["episode"].Success ? int.Parse(match.Groups["episode"].Value) : -1; string language = match.Groups["language"].Value; bool forced = match.Groups["forced"].Success; + if (match.Groups["type"].Success) + type = Enum.Parse(match.Groups["type"].Value, true); if (type == StreamType.Unknown) { @@ -94,7 +97,13 @@ namespace Kyoo.Controllers await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated track (slug {obj.Slug} already exists)."); + await _database.SaveOrRetry(obj, (x, i) => + { + if (i > 10) + throw new DuplicatedItemException($"More than 10 same tracks exists {x.Slug}. Aborting..."); + x.TrackIndex++; + return x; + }); return obj; } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index e03f6677..25271455 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -185,6 +185,9 @@ namespace Kyoo modelBuilder.Entity() .HasIndex(x => new {x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber}) .IsUnique(); + modelBuilder.Entity() + .HasIndex(x => new {x.EpisodeID, x.Type, x.Language, x.TrackIndex, x.IsForced}) + .IsUnique(); } public T GetTemporaryObject(T model) @@ -301,6 +304,33 @@ namespace Kyoo } } + public Task SaveOrRetry(T obj, Func onFail, CancellationToken cancellationToken = new()) + { + return SaveOrRetry(obj, onFail, 0, cancellationToken); + } + + public async Task SaveOrRetry(T obj, + Func onFail, + int recurse, + CancellationToken cancellationToken = new()) + { + try + { + await base.SaveChangesAsync(true, cancellationToken); + return obj; + } + catch (DbUpdateException ex) when (IsDuplicateException(ex)) + { + recurse++; + return await SaveOrRetry(onFail(obj, recurse), onFail, recurse, cancellationToken); + } + catch (DbUpdateException) + { + DiscardChanges(); + throw; + } + } + private static bool IsDuplicateException(Exception ex) { return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation}; diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210317220956_Initial.Designer.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210317220956_Initial.Designer.cs index d62ff33f..b713dc6b 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210317220956_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210317180448_Initial")] + [Migration("20210317220956_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -499,7 +499,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasKey("ID"); - b.HasIndex("EpisodeID"); + b.HasIndex("EpisodeID", "Type", "Language", "TrackIndex", "IsForced") + .IsUnique(); b.ToTable("Tracks"); }); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210317220956_Initial.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210317220956_Initial.cs index 0593207e..b464f251 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210317180448_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210317220956_Initial.cs @@ -543,9 +543,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal unique: true); migrationBuilder.CreateIndex( - name: "IX_Tracks_EpisodeID", + name: "IX_Tracks_EpisodeID_Type_Language_TrackIndex_IsForced", table: "Tracks", - column: "EpisodeID"); + columns: new[] { "EpisodeID", "Type", "Language", "TrackIndex", "IsForced" }, + unique: true); } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 9889f943..f3b66bd3 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -497,7 +497,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasKey("ID"); - b.HasIndex("EpisodeID"); + b.HasIndex("EpisodeID", "Type", "Language", "TrackIndex", "IsForced") + .IsUnique(); b.ToTable("Tracks"); }); diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 03b63cdf..5135cd06 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -196,7 +196,7 @@ namespace Kyoo ctx.Response.Headers.Remove("X-Powered-By"); ctx.Response.Headers.Remove("Server"); ctx.Response.Headers.Add("Feature-Policy", "autoplay 'self'; fullscreen"); - ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"); + ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' blob: 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"); ctx.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); ctx.Response.Headers.Add("Referrer-Policy", "no-referrer"); ctx.Response.Headers.Add("Access-Control-Allow-Origin", "null"); diff --git a/Kyoo/Views/API/SubtitleApi.cs b/Kyoo/Views/API/SubtitleApi.cs index 132b3b19..8cdc124b 100644 --- a/Kyoo/Views/API/SubtitleApi.cs +++ b/Kyoo/Views/API/SubtitleApi.cs @@ -35,7 +35,7 @@ namespace Kyoo.Api return BadRequest(new {error = ex.Message}); } - if (subtitle == null) + if (subtitle == null || subtitle.Type != StreamType.Subtitle) return NotFound(); if (subtitle.Codec == "subrip" && extension == "vtt") From aeed35bea6ac61dd6afc93deca89e84e010bfc44 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 18 Mar 2021 10:56:14 +0100 Subject: [PATCH 48/54] Removing database logs --- Kyoo.Common/Models/Resources/Track.cs | 2 +- Kyoo/Startup.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 544dc40e..07a2efbf 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -77,7 +77,7 @@ namespace Kyoo.Models string language = GetLanguage(Language); if (language == null) - return $"Unknown Language (id: {ID.ToString()})"; + return $"Unknown (index: {TrackIndex})"; CultureInfo info = CultureInfo.GetCultures(CultureTypes.NeutralCultures) .FirstOrDefault(x => x.ThreeLetterISOLanguageName == language); string name = info?.EnglishName ?? language; diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 5135cd06..9c39498d 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -58,9 +58,9 @@ namespace Kyoo services.AddDbContext(options => { - options.UseNpgsql(_configuration.GetConnectionString("Database")) - .EnableSensitiveDataLogging() - .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); + options.UseNpgsql(_configuration.GetConnectionString("Database")); + // .EnableSensitiveDataLogging() + // .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); }, ServiceLifetime.Transient); services.AddDbContext(options => From fdfd42c7c1eade4583a508efee1c3ac349409390 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 18 Mar 2021 11:40:43 +0100 Subject: [PATCH 49/54] Fixing collection's link & cleaning library's link --- Kyoo.Common/Models/Link.cs | 16 +++++++++++--- .../Repositories/ShowRepository.cs | 21 ++++++------------- Kyoo/Models/DatabaseContext.cs | 10 +++++++-- Kyoo/Views/WebClient | 2 +- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Kyoo.Common/Models/Link.cs b/Kyoo.Common/Models/Link.cs index cacc7c64..f504b5a0 100644 --- a/Kyoo.Common/Models/Link.cs +++ b/Kyoo.Common/Models/Link.cs @@ -8,7 +8,13 @@ namespace Kyoo.Models public int FirstID { get; set; } public int SecondID { get; set; } - public Link() {} + public Link() {} + + public Link(int firstID, int secondID) + { + FirstID = firstID; + SecondID = secondID; + } public Link(IResource first, IResource second) { @@ -39,7 +45,7 @@ namespace Kyoo.Models { get { - return x => new {LibraryID = x.FirstID, ProviderID = x.SecondID}; + return x => new {First = x.FirstID, Second = x.SecondID}; } } } @@ -63,11 +69,15 @@ namespace Kyoo.Models Second = second; } + public Link(int firstID, int secondID) + : base(firstID, secondID) + { } + public new static Expression, object>> PrimaryKey { get { - return x => new {LibraryID = x.FirstID, ProviderID = x.SecondID}; + return x => new {First = x.FirstID, Second = x.SecondID}; } } } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index be04ffc9..f07e4f5a 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; -using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -148,31 +147,23 @@ namespace Kyoo.Controllers public async Task AddShowLink(int showID, int? libraryID, int? collectionID) { - Show show = await Get(showID); if (collectionID != null) { - Collection collection = _database.GetTemporaryObject(new Collection {ID = collectionID.Value}); - - show.Collections ??= new List(); - show.Collections.Add(collection); + await _database.Links() + .AddAsync(new Link(collectionID.Value, showID)); await _database.SaveIfNoDuplicates(); if (libraryID != null) { - Library library = await _database.Libraries.FirstOrDefaultAsync(x => x.ID == libraryID.Value); - if (library == null) - throw new ItemNotFound($"No library found with the ID {libraryID.Value}"); - library.Collections ??= new List(); - library.Collections.Add(collection); + await _database.Links() + .AddAsync(new Link(libraryID.Value, collectionID.Value)); await _database.SaveIfNoDuplicates(); } } if (libraryID != null) { - Library library = _database.GetTemporaryObject(new Library {ID = libraryID.Value}); - - show.Libraries ??= new List(); - show.Libraries.Add(library); + await _database.Links() + .AddAsync(new Link(libraryID.Value, showID)); await _database.SaveIfNoDuplicates(); } } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 25271455..435b01f7 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -30,8 +30,14 @@ namespace Kyoo public DbSet Providers { get; set; } public DbSet MetadataIds { get; set; } - // TODO Many to many with UsingEntity for this. public DbSet PeopleRoles { get; set; } + + public DbSet> Links() + where T1 : class, IResource + where T2 : class, IResource + { + return Set>(); + } public DatabaseContext() @@ -115,7 +121,7 @@ namespace Kyoo .HasOne(x => x.Second) .WithMany(x => x.CollectionLinks), y => y.HasKey(Link.PrimaryKey)); - + modelBuilder.Entity() .HasMany(x => x.Shows) .WithMany(x => x.Genres) diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index f36ac1bb..a8a576bd 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit f36ac1bb9bf60329a7f04ba76730f43ded7b0d9d +Subproject commit a8a576bdd9e0c69fbae8e5e4f392c112fe3af8a2 From 57e49b7e838c230f75892d7ca69ce68f175f8832 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 22 Mar 2021 02:52:18 +0100 Subject: [PATCH 50/54] Reworking the transcoder and creating a file manager --- Kyoo.Common/Controllers/IFileManager.cs | 16 +++ Kyoo.Common/Controllers/ITranscoder.cs | 3 +- Kyoo.Common/Kyoo.Common.csproj | 1 + Kyoo.Common/Models/Resources/Track.cs | 4 +- Kyoo/Controllers/FileManager.cs | 41 ++++++ Kyoo/Controllers/Transcoder.cs | 134 +++++++++++++++++++ Kyoo/Controllers/Transcoder/Transcoder.cs | 70 ---------- Kyoo/Controllers/Transcoder/TranscoderAPI.cs | 60 --------- Kyoo/Startup.cs | 1 + Kyoo/Tasks/Crawler.cs | 4 +- Kyoo/Tasks/ExtractMetadata.cs | 4 +- Kyoo/Views/API/SubtitleApi.cs | 21 +-- Kyoo/Views/API/VideoApi.cs | 54 +++----- transcoder | 2 +- 14 files changed, 235 insertions(+), 180 deletions(-) create mode 100644 Kyoo.Common/Controllers/IFileManager.cs create mode 100644 Kyoo/Controllers/FileManager.cs create mode 100644 Kyoo/Controllers/Transcoder.cs delete mode 100644 Kyoo/Controllers/Transcoder/Transcoder.cs delete mode 100644 Kyoo/Controllers/Transcoder/TranscoderAPI.cs diff --git a/Kyoo.Common/Controllers/IFileManager.cs b/Kyoo.Common/Controllers/IFileManager.cs new file mode 100644 index 00000000..8e2d52fb --- /dev/null +++ b/Kyoo.Common/Controllers/IFileManager.cs @@ -0,0 +1,16 @@ +using System.IO; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc; + +namespace Kyoo.Controllers +{ + public interface IFileManager + { + public IActionResult FileResult([NotNull] string path, bool rangeSupport = false); + + public StreamReader GetReader([NotNull] string path); + // TODO implement a List for directorys, a Exist to check existance and all. + // TODO replace every use of System.IO with this to allow custom paths (like uptobox://path) + // TODO find a way to handle Transmux/Transcode with this system. + } +} \ No newline at end of file diff --git a/Kyoo.Common/Controllers/ITranscoder.cs b/Kyoo.Common/Controllers/ITranscoder.cs index 0eee0f59..afaa524f 100644 --- a/Kyoo.Common/Controllers/ITranscoder.cs +++ b/Kyoo.Common/Controllers/ITranscoder.cs @@ -1,12 +1,11 @@ using Kyoo.Models; -using Kyoo.Models.Watch; using System.Threading.Tasks; namespace Kyoo.Controllers { public interface ITranscoder { - Task ExtractInfos(string path); + Task ExtractInfos(string path, bool reextract); Task Transmux(Episode episode); Task Transcode(Episode episode); } diff --git a/Kyoo.Common/Kyoo.Common.csproj b/Kyoo.Common/Kyoo.Common.csproj index 59d84b4b..9a1ba82b 100644 --- a/Kyoo.Common/Kyoo.Common.csproj +++ b/Kyoo.Common/Kyoo.Common.csproj @@ -22,6 +22,7 @@ + diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 07a2efbf..92438e0a 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -12,7 +12,7 @@ namespace Kyoo.Models Video = 1, Audio = 2, Subtitle = 3, - Font = 4 + Attachment = 4 } namespace Watch @@ -100,7 +100,7 @@ namespace Kyoo.Models StreamType.Subtitle => "", StreamType.Video => "video.", StreamType.Audio => "audio.", - StreamType.Font => "font.", + StreamType.Attachment => "font.", _ => "" }; string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty; diff --git a/Kyoo/Controllers/FileManager.cs b/Kyoo/Controllers/FileManager.cs new file mode 100644 index 00000000..e6be6f6d --- /dev/null +++ b/Kyoo/Controllers/FileManager.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; + +namespace Kyoo.Controllers +{ + public class FileManager : ControllerBase, IFileManager + { + private FileExtensionContentTypeProvider _provider; + + private string _GetContentType(string path) + { + if (_provider == null) + { + _provider = new FileExtensionContentTypeProvider(); + _provider.Mappings[".mkv"] = "video/x-matroska"; + } + + if (_provider.TryGetContentType(path, out string contentType)) + return contentType; + return "video/mp4"; + } + + public IActionResult FileResult(string path, bool range) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + if (!System.IO.File.Exists(path)) + return NotFound(); + return PhysicalFile(path, _GetContentType(path), range); + } + + public StreamReader GetReader(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + return new StreamReader(path); + } + } +} \ No newline at end of file diff --git a/Kyoo/Controllers/Transcoder.cs b/Kyoo/Controllers/Transcoder.cs new file mode 100644 index 00000000..25e1722b --- /dev/null +++ b/Kyoo/Controllers/Transcoder.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Kyoo.Models; +using Microsoft.Extensions.Configuration; +using Stream = Kyoo.Models.Watch.Stream; + +// We use threads so tasks are not always awaited. +#pragma warning disable 4014 + +namespace Kyoo.Controllers +{ + public class BadTranscoderException : Exception {} + + public class Transcoder : ITranscoder + { + private static class TranscoderAPI + { + private const string TranscoderPath = "libtranscoder.so"; + + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] + public static extern int init(); + + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] + public static extern int transmux(string path, string outpath, out float playableDuration); + + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] + public static extern int transcode(string path, string outpath, out float playableDuration); + + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr extract_infos(string path, + string outpath, + out int length, + out int trackCount, + bool reextracct); + + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] + private static extern void free(IntPtr ptr); + + + public static Track[] ExtractInfos(string path, string outPath, bool reextract) + { + int size = Marshal.SizeOf(); + IntPtr ptr = extract_infos(path, outPath, out int arrayLength, out int trackCount, reextract); + IntPtr streamsPtr = ptr; + Track[] tracks; + + if (trackCount > 0 && ptr != IntPtr.Zero) + { + tracks = new Track[trackCount]; + + int j = 0; + for (int i = 0; i < arrayLength; i++) + { + Stream stream = Marshal.PtrToStructure(streamsPtr); + if (stream!.Type != StreamType.Unknown) + { + tracks[j] = new Track(stream); + j++; + } + streamsPtr += size; + } + } + else + tracks = Array.Empty(); + + if (ptr != IntPtr.Zero) + free(ptr); // free_streams is not necesarry since the Marshal free the unmanaged pointers. + return tracks; + } + } + + private readonly string _transmuxPath; + private readonly string _transcodePath; + + public Transcoder(IConfiguration config) + { + _transmuxPath = Path.GetFullPath(config.GetValue("transmuxTempPath")); + _transcodePath = Path.GetFullPath(config.GetValue("transcodeTempPath")); + + if (TranscoderAPI.init() != Marshal.SizeOf()) + throw new BadTranscoderException(); + } + + public Task ExtractInfos(string path, bool reextract) + { + string dir = Path.GetDirectoryName(path); + if (dir == null) + throw new ArgumentException("Invalid path."); + dir = Path.Combine(dir, "Extra"); + return Task.Factory.StartNew( + () => TranscoderAPI.ExtractInfos(path, dir, reextract), + TaskCreationOptions.LongRunning); + } + + public async Task Transmux(Episode episode) + { + if (!File.Exists(episode.Path)) + throw new ArgumentException("Path does not exists. Can't transcode."); + + string folder = Path.Combine(_transmuxPath, episode.Slug); + string manifest = Path.Combine(folder, episode.Slug + ".m3u8"); + float playableDuration = 0; + bool transmuxFailed = false; + + try + { + Directory.CreateDirectory(folder); + if (File.Exists(manifest)) + return manifest; + } + catch (UnauthorizedAccessException) + { + await Console.Error.WriteLineAsync($"Access to the path {manifest} is denied. Please change your transmux path in the config."); + return null; + } + + Task.Factory.StartNew(() => + { + string cleanManifest = manifest.Replace('\\', '/'); + transmuxFailed = TranscoderAPI.transmux(episode.Path, cleanManifest, out playableDuration) != 0; + }, TaskCreationOptions.LongRunning); + while (playableDuration < 10 || !File.Exists(manifest) && !transmuxFailed) + await Task.Delay(10); + return transmuxFailed ? null : manifest; + } + + public Task Transcode(Episode episode) + { + return Task.FromResult(null); // Not implemented yet. + } + } +} diff --git a/Kyoo/Controllers/Transcoder/Transcoder.cs b/Kyoo/Controllers/Transcoder/Transcoder.cs deleted file mode 100644 index 479ac32b..00000000 --- a/Kyoo/Controllers/Transcoder/Transcoder.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using Kyoo.Models; -using Microsoft.Extensions.Configuration; -using System.IO; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Kyoo.Controllers.TranscoderLink; -#pragma warning disable 4014 - -namespace Kyoo.Controllers -{ - public class BadTranscoderException : Exception {} - - public class Transcoder : ITranscoder - { - private readonly string _transmuxPath; - private readonly string _transcodePath; - - public Transcoder(IConfiguration config) - { - _transmuxPath = Path.GetFullPath(config.GetValue("transmuxTempPath")); - _transcodePath = Path.GetFullPath(config.GetValue("transcodeTempPath")); - - if (TranscoderAPI.init() != Marshal.SizeOf()) - throw new BadTranscoderException(); - } - - public Task ExtractInfos(string path) - { - string dir = Path.GetDirectoryName(path); - if (dir == null) - throw new ArgumentException("Invalid path."); - - return Task.Factory.StartNew(() => TranscoderAPI.ExtractInfos(path, dir), TaskCreationOptions.LongRunning); - } - - public async Task Transmux(Episode episode) - { - string folder = Path.Combine(_transmuxPath, episode.Slug); - string manifest = Path.Combine(folder, episode.Slug + ".m3u8"); - float playableDuration = 0; - bool transmuxFailed = false; - - try - { - Directory.CreateDirectory(folder); - if (File.Exists(manifest)) - return manifest; - } - catch (UnauthorizedAccessException) - { - await Console.Error.WriteLineAsync($"Access to the path {manifest} is denied. Please change your transmux path in the config."); - return null; - } - - Task.Factory.StartNew(() => - { - transmuxFailed = TranscoderAPI.transmux(episode.Path, manifest.Replace('\\', '/'), out playableDuration) != 0; - }, TaskCreationOptions.LongRunning); - while (playableDuration < 10 || !File.Exists(manifest) && !transmuxFailed) - await Task.Delay(10); - return transmuxFailed ? null : manifest; - } - - public Task Transcode(Episode episode) - { - return Task.FromResult(null); // Not implemented yet. - } - } -} diff --git a/Kyoo/Controllers/Transcoder/TranscoderAPI.cs b/Kyoo/Controllers/Transcoder/TranscoderAPI.cs deleted file mode 100644 index 6abb5a51..00000000 --- a/Kyoo/Controllers/Transcoder/TranscoderAPI.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Kyoo.Models; -using Kyoo.Models.Watch; -// ReSharper disable InconsistentNaming - -namespace Kyoo.Controllers.TranscoderLink -{ - public static class TranscoderAPI - { - private const string TranscoderPath = "libtranscoder.so"; - - [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] - public static extern int init(); - - [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] - public static extern int transmux(string path, string out_path, out float playableDuration); - - [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] - public static extern int transcode(string path, string out_path, out float playableDuration); - - [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr extract_infos(string path, string outpath, out int length, out int track_count); - - [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] - private static extern void free(IntPtr stream_ptr); - - - public static Track[] ExtractInfos(string path, string outPath) - { - int size = Marshal.SizeOf(); - IntPtr ptr = extract_infos(path, outPath, out int arrayLength, out int trackCount); - IntPtr streamsPtr = ptr; - Track[] tracks; - - if (trackCount > 0 && ptr != IntPtr.Zero) - { - tracks = new Track[trackCount]; - - int j = 0; - for (int i = 0; i < arrayLength; i++) - { - Stream stream = Marshal.PtrToStructure(streamsPtr); - if (stream!.Type != StreamType.Unknown) - { - tracks[j] = new Track(stream); - j++; - } - streamsPtr += size; - } - } - else - tracks = Array.Empty(); - - if (ptr != IntPtr.Zero) - free(ptr); // free_streams is not necesarry since the Marshal free the unmanaged pointers. - return tracks; - } - } -} diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 9c39498d..475f8691 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -158,6 +158,7 @@ namespace Kyoo services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 0c0db5c4..853ea5a9 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -359,8 +359,8 @@ namespace Kyoo.Controllers private async Task> GetTracks(Episode episode) { - episode.Tracks = (await _transcoder.ExtractInfos(episode.Path)) - .Where(x => x.Type != StreamType.Font) + episode.Tracks = (await _transcoder.ExtractInfos(episode.Path, false)) + .Where(x => x.Type != StreamType.Attachment) .ToArray(); return episode.Tracks; } diff --git a/Kyoo/Tasks/ExtractMetadata.cs b/Kyoo/Tasks/ExtractMetadata.cs index 4dccc70f..a36659f4 100644 --- a/Kyoo/Tasks/ExtractMetadata.cs +++ b/Kyoo/Tasks/ExtractMetadata.cs @@ -101,8 +101,8 @@ namespace Kyoo.Tasks if (subs) { await _library.Load(episode, x => x.Tracks); - episode.Tracks = (await _transcoder!.ExtractInfos(episode.Path)) - .Where(x => x.Type != StreamType.Font) + episode.Tracks = (await _transcoder!.ExtractInfos(episode.Path, true)) + .Where(x => x.Type != StreamType.Attachment) .Concat(episode.Tracks.Where(x => x.IsExternal)) .ToList(); await _library.EditEpisode(episode, false); diff --git a/Kyoo/Views/API/SubtitleApi.cs b/Kyoo/Views/API/SubtitleApi.cs index 8cdc124b..73f151ee 100644 --- a/Kyoo/Views/API/SubtitleApi.cs +++ b/Kyoo/Views/API/SubtitleApi.cs @@ -14,10 +14,12 @@ namespace Kyoo.Api public class SubtitleApi : ControllerBase { private readonly ILibraryManager _libraryManager; + private readonly IFileManager _files; - public SubtitleApi(ILibraryManager libraryManager) + public SubtitleApi(ILibraryManager libraryManager, IFileManager files) { _libraryManager = libraryManager; + _files = files; } @@ -39,9 +41,8 @@ namespace Kyoo.Api return NotFound(); if (subtitle.Codec == "subrip" && extension == "vtt") - return new ConvertSubripToVtt(subtitle.Path); - string mime = subtitle.Codec == "ass" ? "text/x-ssa" : "application/x-subrip"; - return PhysicalFile(subtitle.Path, mime); + return new ConvertSubripToVtt(subtitle.Path, _files); + return _files.FileResult(subtitle.Path); } } @@ -49,27 +50,29 @@ namespace Kyoo.Api public class ConvertSubripToVtt : IActionResult { private readonly string _path; + private readonly IFileManager _files; - public ConvertSubripToVtt(string subtitlePath) + public ConvertSubripToVtt(string subtitlePath, IFileManager files) { _path = subtitlePath; + _files = files; } public async Task ExecuteResultAsync(ActionContext context) { - string line; - List lines = new List(); + List lines = new(); context.HttpContext.Response.StatusCode = 200; context.HttpContext.Response.Headers.Add("Content-Type", "text/vtt"); - await using (StreamWriter writer = new StreamWriter(context.HttpContext.Response.Body)) + await using (StreamWriter writer = new(context.HttpContext.Response.Body)) { await writer.WriteLineAsync("WEBVTT"); await writer.WriteLineAsync(""); await writer.WriteLineAsync(""); - using StreamReader reader = new StreamReader(_path); + using StreamReader reader = _files.GetReader(_path); + string line; while ((line = await reader.ReadLineAsync()) != null) { if (line == "") diff --git a/Kyoo/Views/API/VideoApi.cs b/Kyoo/Views/API/VideoApi.cs index f92bdd6a..1e6651b5 100644 --- a/Kyoo/Views/API/VideoApi.cs +++ b/Kyoo/Views/API/VideoApi.cs @@ -1,12 +1,11 @@ -using Kyoo.Controllers; +using System.IO; +using Kyoo.Controllers; using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; -using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.StaticFiles; namespace Kyoo.Api { @@ -16,14 +15,18 @@ namespace Kyoo.Api { private readonly ILibraryManager _libraryManager; private readonly ITranscoder _transcoder; + private readonly IFileManager _files; private readonly string _transmuxPath; private readonly string _transcodePath; - private FileExtensionContentTypeProvider _provider; - public VideoApi(ILibraryManager libraryManager, ITranscoder transcoder, IConfiguration config) + public VideoApi(ILibraryManager libraryManager, + ITranscoder transcoder, + IConfiguration config, + IFileManager files) { _libraryManager = libraryManager; _transcoder = transcoder; + _files = files; _transmuxPath = config.GetValue("transmuxTempPath"); _transcodePath = config.GetValue("transcodeTempPath"); } @@ -37,19 +40,6 @@ namespace Kyoo.Api ctx.HttpContext.Response.Headers.Add("Expires", "0"); } - private string _GetContentType(string path) - { - if (_provider == null) - { - _provider = new FileExtensionContentTypeProvider(); - _provider.Mappings[".mkv"] = "video/x-matroska"; - } - - if (_provider.TryGetContentType(path, out string contentType)) - return contentType; - return "video/mp4"; - } - [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}")] [HttpGet("direct/{showSlug}-s{seasonNumber:int}e{episodeNumber:int}")] @@ -60,9 +50,9 @@ namespace Kyoo.Api return BadRequest(new {error = "Season number or episode number can not be negative."}); Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); - if (episode != null && System.IO.File.Exists(episode.Path)) - return PhysicalFile(episode.Path, _GetContentType(episode.Path), true); - return NotFound(); + if (episode == null) + return NotFound(); + return _files.FileResult(episode.Path, true); } [HttpGet("{movieSlug}")] @@ -72,9 +62,9 @@ namespace Kyoo.Api { Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); - if (episode != null && System.IO.File.Exists(episode.Path)) - return PhysicalFile(episode.Path, _GetContentType(episode.Path), true); - return NotFound(); + if (episode == null) + return NotFound(); + return _files.FileResult(episode.Path, true); } @@ -86,12 +76,12 @@ namespace Kyoo.Api return BadRequest(new {error = "Season number or episode number can not be negative."}); Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); - if (episode == null || !System.IO.File.Exists(episode.Path)) + if (episode == null) return NotFound(); string path = await _transcoder.Transmux(episode); if (path == null) return StatusCode(500); - return PhysicalFile(path, "application/x-mpegurl", true); + return _files.FileResult(path, true); } [HttpGet("transmux/{movieSlug}/master.m3u8")] @@ -100,12 +90,12 @@ namespace Kyoo.Api { Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); - if (episode == null || !System.IO.File.Exists(episode.Path)) + if (episode == null) return NotFound(); string path = await _transcoder.Transmux(episode); if (path == null) return StatusCode(500); - return PhysicalFile(path, "application/x-mpegurl", true); + return _files.FileResult(path, true); } [HttpGet("transcode/{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/master.m3u8")] @@ -116,12 +106,12 @@ namespace Kyoo.Api return BadRequest(new {error = "Season number or episode number can not be negative."}); Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); - if (episode == null || !System.IO.File.Exists(episode.Path)) + if (episode == null) return NotFound(); string path = await _transcoder.Transcode(episode); if (path == null) return StatusCode(500); - return PhysicalFile(path, "application/x-mpegurl", true); + return _files.FileResult(path, true); } [HttpGet("transcode/{movieSlug}/master.m3u8")] @@ -130,12 +120,12 @@ namespace Kyoo.Api { Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); - if (episode == null || !System.IO.File.Exists(episode.Path)) + if (episode == null) return NotFound(); string path = await _transcoder.Transcode(episode); if (path == null) return StatusCode(500); - return PhysicalFile(path, "application/x-mpegurl", true); + return _files.FileResult(path, true); } diff --git a/transcoder b/transcoder index 3885dca7..9acd635a 160000 --- a/transcoder +++ b/transcoder @@ -1 +1 @@ -Subproject commit 3885dca743bbde5d83cb3816646455856fc5c316 +Subproject commit 9acd635aca92ad81f1de562e34b2c7c270bade29 From 365fd1e79ffe0c40e050093fc1d791acb2f9b233 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 23 Mar 2021 00:16:42 +0100 Subject: [PATCH 51/54] Reworking paths management --- Kyoo.Common/Controllers/IFileManager.cs | 7 +- Kyoo.Common/Controllers/ILibraryManager.cs | 2 + Kyoo.Common/Controllers/IThumbnailsManager.cs | 9 ++ .../Implementations/LibraryManager.cs | 10 +++ Kyoo/Controllers/FileManager.cs | 30 +++++-- Kyoo/Controllers/ThumbnailsManager.cs | 85 +++++++++++++++++-- Kyoo/Controllers/Transcoder.cs | 9 +- Kyoo/Startup.cs | 2 +- Kyoo/Views/API/EpisodeApi.cs | 36 ++++---- Kyoo/Views/API/PeopleApi.cs | 25 +++--- Kyoo/Views/API/ProviderApi.cs | 26 +++--- Kyoo/Views/API/SeasonApi.cs | 39 ++++----- Kyoo/Views/API/ShowApi.cs | 64 +++++--------- Kyoo/Views/API/StudioApi.cs | 1 - 14 files changed, 217 insertions(+), 128 deletions(-) diff --git a/Kyoo.Common/Controllers/IFileManager.cs b/Kyoo.Common/Controllers/IFileManager.cs index 8e2d52fb..aa845f82 100644 --- a/Kyoo.Common/Controllers/IFileManager.cs +++ b/Kyoo.Common/Controllers/IFileManager.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.IO; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; @@ -6,11 +7,15 @@ namespace Kyoo.Controllers { public interface IFileManager { - public IActionResult FileResult([NotNull] string path, bool rangeSupport = false); + public IActionResult FileResult([CanBeNull] string path, bool rangeSupport = false); public StreamReader GetReader([NotNull] string path); + + public ICollection ListFiles([NotNull] string path); // TODO implement a List for directorys, a Exist to check existance and all. // TODO replace every use of System.IO with this to allow custom paths (like uptobox://path) // TODO find a way to handle Transmux/Transcode with this system. + + public string GetExtraDirectory(string showPath); } } \ No newline at end of file diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 374f012a..ae1d0ccb 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -35,6 +35,7 @@ namespace Kyoo.Controllers Task GetTrack(int id); Task GetStudio(int id); Task GetPeople(int id); + Task GetProvider(int id); // Get by slug Task GetLibrary(string slug); @@ -49,6 +50,7 @@ namespace Kyoo.Controllers Task GetGenre(string slug); Task GetStudio(string slug); Task GetPeople(string slug); + Task GetProvider(string slug); // Get by predicate Task GetLibrary(Expression> where); diff --git a/Kyoo.Common/Controllers/IThumbnailsManager.cs b/Kyoo.Common/Controllers/IThumbnailsManager.cs index 32666e84..603a0f6f 100644 --- a/Kyoo.Common/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Common/Controllers/IThumbnailsManager.cs @@ -1,6 +1,7 @@ using Kyoo.Models; using System.Collections.Generic; using System.Threading.Tasks; +using JetBrains.Annotations; namespace Kyoo.Controllers { @@ -11,5 +12,13 @@ namespace Kyoo.Controllers Task Validate(Episode episode, bool alwaysDownload = false); Task Validate(People actors, bool alwaysDownload = false); Task Validate(ProviderID actors, bool alwaysDownload = false); + + Task GetShowPoster([NotNull] Show show); + Task GetShowLogo([NotNull] Show show); + Task GetShowBackdrop([NotNull] Show show); + Task GetSeasonPoster([NotNull] Season season); + Task GetEpisodeThumb([NotNull] Episode episode); + Task GetPeoplePoster([NotNull] People people); + Task GetProviderLogo([NotNull] ProviderID provider); } } diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 9a54b7af..7cae9b4d 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -131,6 +131,11 @@ namespace Kyoo.Controllers return PeopleRepository.Get(id); } + public Task GetProvider(int id) + { + return ProviderRepository.Get(id); + } + public Task GetLibrary(string slug) { return LibraryRepository.Get(slug); @@ -190,6 +195,11 @@ namespace Kyoo.Controllers { return PeopleRepository.Get(slug); } + + public Task GetProvider(string slug) + { + return ProviderRepository.Get(slug); + } public Task GetLibrary(Expression> where) { diff --git a/Kyoo/Controllers/FileManager.cs b/Kyoo/Controllers/FileManager.cs index e6be6f6d..a9e52478 100644 --- a/Kyoo/Controllers/FileManager.cs +++ b/Kyoo/Controllers/FileManager.cs @@ -1,11 +1,12 @@ using System; +using System.Collections.Generic; using System.IO; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; namespace Kyoo.Controllers { - public class FileManager : ControllerBase, IFileManager + public class FileManager : IFileManager { private FileExtensionContentTypeProvider _provider; @@ -19,16 +20,19 @@ namespace Kyoo.Controllers if (_provider.TryGetContentType(path, out string contentType)) return contentType; - return "video/mp4"; + throw new NotImplementedException($"Can't get the content type of the file at: {path}"); } public IActionResult FileResult(string path, bool range) { if (path == null) - throw new ArgumentNullException(nameof(path)); - if (!System.IO.File.Exists(path)) - return NotFound(); - return PhysicalFile(path, _GetContentType(path), range); + return new NotFoundResult(); + if (!File.Exists(path)) + return new NotFoundResult(); + return new PhysicalFileResult(Path.GetFullPath(path), _GetContentType(path)) + { + EnableRangeProcessing = range + }; } public StreamReader GetReader(string path) @@ -37,5 +41,19 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(path)); return new StreamReader(path); } + + public string GetExtraDirectory(string showPath) + { + string path = Path.Combine(showPath, "Extra"); + Directory.CreateDirectory(path); + return path; + } + + public ICollection ListFiles(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + return Directory.GetFiles(path); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index a2f0f7ac..b2dc08c7 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -11,10 +11,16 @@ namespace Kyoo.Controllers public class ThumbnailsManager : IThumbnailsManager { private readonly IConfiguration _config; + private readonly IFileManager _files; + private readonly string _peoplePath; + private readonly string _providerPath; - public ThumbnailsManager(IConfiguration configuration) + public ThumbnailsManager(IConfiguration configuration, IFileManager files) { _config = configuration; + _files = files; + _peoplePath = Path.GetFullPath(configuration.GetValue("peoplePath")); + _providerPath = Path.GetFullPath(configuration.GetValue("providerPath")); } private static async Task DownloadImage(string url, string localPath, string what) @@ -34,22 +40,23 @@ namespace Kyoo.Controllers { if (show?.Path == null) return default; + string basePath = _files.GetExtraDirectory(show.Path); if (show.Poster != null) { - string posterPath = Path.Combine(show.Path, "poster.jpg"); + string posterPath = Path.Combine(basePath, "poster.jpg"); if (alwaysDownload || !File.Exists(posterPath)) await DownloadImage(show.Poster, posterPath, $"The poster of {show.Title}"); } if (show.Logo != null) { - string logoPath = Path.Combine(show.Path, "logo.png"); + string logoPath = Path.Combine(basePath, "logo.png"); if (alwaysDownload || !File.Exists(logoPath)) await DownloadImage(show.Logo, logoPath, $"The logo of {show.Title}"); } if (show.Backdrop != null) { - string backdropPath = Path.Combine(show.Path, "backdrop.jpg"); + string backdropPath = Path.Combine(basePath, "backdrop.jpg"); if (alwaysDownload || !File.Exists(backdropPath)) await DownloadImage(show.Backdrop, backdropPath, $"The backdrop of {show.Title}"); } @@ -81,7 +88,8 @@ namespace Kyoo.Controllers if (season.Poster != null) { - string localPath = Path.Combine(season.Show.Path, $"season-{season.SeasonNumber}.jpg"); + string basePath = _files.GetExtraDirectory(season.Show.Path); + string localPath = Path.Combine(basePath, $"season-{season.SeasonNumber}.jpg"); if (alwaysDownload || !File.Exists(localPath)) await DownloadImage(season.Poster, localPath, $"The poster of {season.Show.Title}'s season {season.SeasonNumber}"); } @@ -95,7 +103,10 @@ namespace Kyoo.Controllers if (episode.Thumb != null) { - string localPath = Path.ChangeExtension(episode.Path, "jpg"); + string localPath = Path.Combine( + _files.GetExtraDirectory(Path.GetDirectoryName(episode.Path)), + "Thumbnails", + $"{Path.GetFileNameWithoutExtension(episode.Path)}.jpg"); if (alwaysDownload || !File.Exists(localPath)) await DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}"); } @@ -115,7 +126,67 @@ namespace Kyoo.Controllers await DownloadImage(provider.Logo, localPath, $"The logo of {provider.Slug}"); return provider; } + + public Task GetShowBackdrop(Show show) + { + if (show?.Path == null) + throw new ArgumentNullException(nameof(show)); + return Task.FromResult(Path.Combine(_files.GetExtraDirectory(show.Path), "backdrop.jpg")); + } - //TODO add get thumbs here + public Task GetShowLogo(Show show) + { + if (show?.Path == null) + throw new ArgumentNullException(nameof(show)); + return Task.FromResult(Path.Combine(_files.GetExtraDirectory(show.Path), "logo.png")); + } + + public Task GetShowPoster(Show show) + { + if (show?.Path == null) + throw new ArgumentNullException(nameof(show)); + return Task.FromResult(Path.Combine(_files.GetExtraDirectory(show.Path), "poster.jpg")); + } + + public Task GetSeasonPoster(Season season) + { + if (season == null) + throw new ArgumentNullException(nameof(season)); + // TODO Use a season.Path (for season's folder) + string path = season.Show.Poster; + if (path == null) + return Task.FromResult(null); + + string thumb = Path.Combine(_files.GetExtraDirectory(path), $"season-{season.SeasonNumber}.jpg"); + return Task.FromResult(File.Exists(thumb) ? Path.GetFullPath(thumb) : null); + } + + public Task GetEpisodeThumb(Episode episode) + { + string path = episode.Path; + // TODO use show's path for get extra directory. If seasons folder are used, episodes may not be directly in the show folder. + return Task.FromResult(Path.Combine( + _files.GetExtraDirectory(Path.GetDirectoryName(path)), + "Thumbnails", + $"{Path.GetFileNameWithoutExtension(path)}.jpg")); + } + + public Task GetPeoplePoster(People people) + { + if (people == null) + throw new ArgumentNullException(nameof(people)); + string thumbPath = Path.GetFullPath(Path.Combine(_peoplePath, $"{people.Slug}.jpg")); + if (!thumbPath.StartsWith(_peoplePath) || File.Exists(thumbPath)) + return Task.FromResult(null); + return Task.FromResult(thumbPath); + } + + public Task GetProviderLogo(ProviderID provider) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + string thumbPath = Path.GetFullPath(Path.Combine(_providerPath, $"{provider.Slug}.jpg")); + return Task.FromResult(thumbPath.StartsWith(_providerPath) ? thumbPath : null); + } } } diff --git a/Kyoo/Controllers/Transcoder.cs b/Kyoo/Controllers/Transcoder.cs index 25e1722b..26a05e0e 100644 --- a/Kyoo/Controllers/Transcoder.cs +++ b/Kyoo/Controllers/Transcoder.cs @@ -70,12 +70,14 @@ namespace Kyoo.Controllers return tracks; } } - + + private readonly IFileManager _files; private readonly string _transmuxPath; private readonly string _transcodePath; - public Transcoder(IConfiguration config) + public Transcoder(IConfiguration config, IFileManager files) { + _files = files; _transmuxPath = Path.GetFullPath(config.GetValue("transmuxTempPath")); _transcodePath = Path.GetFullPath(config.GetValue("transcodeTempPath")); @@ -85,9 +87,10 @@ namespace Kyoo.Controllers public Task ExtractInfos(string path, bool reextract) { - string dir = Path.GetDirectoryName(path); + string dir = _files.GetExtraDirectory(path); if (dir == null) throw new ArgumentException("Invalid path."); + // TODO invalid path here. dir = Path.Combine(dir, "Extra"); return Task.Factory.StartNew( () => TranscoderAPI.ExtractInfos(path, dir, reextract), diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 475f8691..7f25909c 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -158,7 +158,7 @@ namespace Kyoo services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/Kyoo/Views/API/EpisodeApi.cs b/Kyoo/Views/API/EpisodeApi.cs index de9ae424..c0c01a18 100644 --- a/Kyoo/Views/API/EpisodeApi.cs +++ b/Kyoo/Views/API/EpisodeApi.cs @@ -2,7 +2,6 @@ using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using Kyoo.CommonApi; @@ -18,11 +17,18 @@ namespace Kyoo.Api public class EpisodeApi : CrudApi { private readonly ILibraryManager _libraryManager; + private readonly IThumbnailsManager _thumbnails; + private readonly IFileManager _files; - public EpisodeApi(ILibraryManager libraryManager, IConfiguration configuration) + public EpisodeApi(ILibraryManager libraryManager, + IConfiguration configuration, + IFileManager files, + IThumbnailsManager thumbnails) : base(libraryManager.EpisodeRepository, configuration) { _libraryManager = libraryManager; + _files = files; + _thumbnails = thumbnails; } [HttpGet("{episodeID:int}/show")] @@ -156,30 +162,20 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetThumb(int id) { - string path = (await _libraryManager.GetEpisode(id))?.Path; - if (path == null) + Episode episode = await _libraryManager.GetEpisode(id); + if (episode == null) return NotFound(); - - string thumb = Path.ChangeExtension(path, "jpg"); - - if (System.IO.File.Exists(thumb)) - return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); - return NotFound(); + return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode)); } - [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/thumb")] + [HttpGet("{slug}/thumb")] [Authorize(Policy="Read")] - public async Task GetThumb(string showSlug, int seasonNumber, int episodeNumber) + public async Task GetThumb(string slug) { - string path = (await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber))?.Path; - if (path == null) + Episode episode = await _libraryManager.GetEpisode(slug); + if (episode == null) return NotFound(); - - string thumb = Path.ChangeExtension(path, "jpg"); - - if (System.IO.File.Exists(thumb)) - return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); - return NotFound(); + return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode)); } } } \ No newline at end of file diff --git a/Kyoo/Views/API/PeopleApi.cs b/Kyoo/Views/API/PeopleApi.cs index 602c022c..bfcb8a29 100644 --- a/Kyoo/Views/API/PeopleApi.cs +++ b/Kyoo/Views/API/PeopleApi.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; @@ -17,13 +16,18 @@ namespace Kyoo.Api public class PeopleApi : CrudApi { private readonly ILibraryManager _libraryManager; - private readonly string _peoplePath; + private readonly IFileManager _files; + private readonly IThumbnailsManager _thumbs; - public PeopleApi(ILibraryManager libraryManager, IConfiguration configuration) + public PeopleApi(ILibraryManager libraryManager, + IConfiguration configuration, + IFileManager files, + IThumbnailsManager thumbs) : base(libraryManager.PeopleRepository, configuration) { _libraryManager = libraryManager; - _peoplePath = Path.GetFullPath(configuration.GetValue("peoplePath")); + _files = files; + _thumbs = thumbs; } [HttpGet("{id:int}/role")] @@ -86,19 +90,16 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetPeopleIcon(int id) { - string slug = (await _libraryManager.GetPeople(id)).Slug; - return GetPeopleIcon(slug); + People people = await _libraryManager.GetPeople(id); + return _files.FileResult(await _thumbs.GetPeoplePoster(people)); } [HttpGet("{slug}/poster")] [Authorize(Policy="Read")] - public IActionResult GetPeopleIcon(string slug) + public async Task GetPeopleIcon(string slug) { - string thumbPath = Path.GetFullPath(Path.Combine(_peoplePath, slug + ".jpg")); - if (!thumbPath.StartsWith(_peoplePath) || !System.IO.File.Exists(thumbPath)) - return NotFound(); - - return new PhysicalFileResult(Path.GetFullPath(thumbPath), "image/jpg"); + People people = await _libraryManager.GetPeople(slug); + return _files.FileResult(await _thumbs.GetPeoplePoster(people)); } } } \ No newline at end of file diff --git a/Kyoo/Views/API/ProviderApi.cs b/Kyoo/Views/API/ProviderApi.cs index 6d1cbb86..2d3aab3d 100644 --- a/Kyoo/Views/API/ProviderApi.cs +++ b/Kyoo/Views/API/ProviderApi.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; @@ -15,33 +13,35 @@ namespace Kyoo.Api [ApiController] public class ProviderAPI : CrudApi { + private readonly IThumbnailsManager _thumbnails; private readonly ILibraryManager _libraryManager; - private readonly string _providerPath; + private readonly IFileManager _files; - public ProviderAPI(ILibraryManager libraryManager, IConfiguration config) + public ProviderAPI(ILibraryManager libraryManager, + IConfiguration config, + IFileManager files, + IThumbnailsManager thumbnails) : base(libraryManager.ProviderRepository, config) { _libraryManager = libraryManager; - _providerPath = Path.GetFullPath(config.GetValue("providerPath")); + _files = files; + _thumbnails = thumbnails; } [HttpGet("{id:int}/logo")] [Authorize(Policy="Read")] public async Task GetLogo(int id) { - string slug = (await _libraryManager.GetPeople(id)).Slug; - return GetLogo(slug); + ProviderID provider = await _libraryManager.GetProvider(id); + return _files.FileResult(await _thumbnails.GetProviderLogo(provider)); } [HttpGet("{slug}/logo")] [Authorize(Policy="Read")] - public IActionResult GetLogo(string slug) + public async Task GetLogo(string slug) { - string thumbPath = Path.GetFullPath(Path.Combine(_providerPath, slug + ".jpg")); - if (!thumbPath.StartsWith(_providerPath) || !System.IO.File.Exists(thumbPath)) - return NotFound(); - - return new PhysicalFileResult(Path.GetFullPath(thumbPath), "image/jpg"); + ProviderID provider = await _libraryManager.GetProvider(slug); + return _files.FileResult(await _thumbnails.GetProviderLogo(provider)); } } } \ No newline at end of file diff --git a/Kyoo/Views/API/SeasonApi.cs b/Kyoo/Views/API/SeasonApi.cs index 4bedf5ec..bc43cad5 100644 --- a/Kyoo/Views/API/SeasonApi.cs +++ b/Kyoo/Views/API/SeasonApi.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; @@ -18,11 +17,18 @@ namespace Kyoo.Api public class SeasonApi : CrudApi { private readonly ILibraryManager _libraryManager; + private readonly IThumbnailsManager _thumbs; + private readonly IFileManager _files; - public SeasonApi(ILibraryManager libraryManager, IConfiguration configuration) + public SeasonApi(ILibraryManager libraryManager, + IConfiguration configuration, + IThumbnailsManager thumbs, + IFileManager files) : base(libraryManager.SeasonRepository, configuration) { _libraryManager = libraryManager; + _thumbs = thumbs; + _files = files; } [HttpGet("{seasonID:int}/episode")] @@ -131,31 +137,18 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetThumb(int id) { - // TODO remove the next lambda and use a Season.Path (should exit for seasons in a different folder) - string path = (await _libraryManager.GetShow(x => x.Seasons.Any(y => y.ID == id)))?.Path; - int seasonNumber = (await _libraryManager.GetSeason(id)).SeasonNumber; - if (path == null) - return NotFound(); - - string thumb = Path.Combine(path, $"season-{seasonNumber}.jpg"); - if (System.IO.File.Exists(thumb)) - return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); - return NotFound(); + Season season = await _libraryManager.GetSeason(id); + await _libraryManager.Load(season, x => x.Show); + return _files.FileResult(await _thumbs.GetSeasonPoster(season)); } - [HttpGet("{showSlug}-s{seasonNumber:int}/thumb")] + [HttpGet("{slug}/thumb")] [Authorize(Policy="Read")] - public async Task GetThumb(string showSlug, int seasonNumber) + public async Task GetThumb(string slug) { - // TODO use a season.Path - string path = (await _libraryManager.GetShow(showSlug))?.Path; - if (path == null) - return NotFound(); - - string thumb = Path.Combine(path, $"season-{seasonNumber}.jpg"); - if (System.IO.File.Exists(thumb)) - return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); - return NotFound(); + Season season = await _libraryManager.GetSeason(slug); + await _libraryManager.Load(season, x => x.Show); + return _files.FileResult(await _thumbs.GetSeasonPoster(season)); } } } \ No newline at end of file diff --git a/Kyoo/Views/API/ShowApi.cs b/Kyoo/Views/API/ShowApi.cs index 28b6ef94..5ab63a00 100644 --- a/Kyoo/Views/API/ShowApi.cs +++ b/Kyoo/Views/API/ShowApi.cs @@ -9,7 +9,6 @@ using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Configuration; namespace Kyoo.Api @@ -20,12 +19,18 @@ namespace Kyoo.Api public class ShowApi : CrudApi { private readonly ILibraryManager _libraryManager; - private FileExtensionContentTypeProvider _provider; + private readonly IFileManager _files; + private readonly IThumbnailsManager _thumbs; - public ShowApi(ILibraryManager libraryManager, IConfiguration configuration) + public ShowApi(ILibraryManager libraryManager, + IFileManager files, + IThumbnailsManager thumbs, + IConfiguration configuration) : base(libraryManager.ShowRepository, configuration) { _libraryManager = libraryManager; + _files = files; + _thumbs = thumbs; } [HttpGet("{showID:int}/season")] @@ -374,10 +379,8 @@ namespace Kyoo.Api string path = (await _libraryManager.GetShow(slug))?.Path; if (path == null) return NotFound(); - path = Path.Combine(path, "Subtitles", "fonts"); - if (!Directory.Exists(path)) - return new Dictionary(); - return Directory.GetFiles(path) + path = Path.Combine(_files.GetExtraDirectory(path), "Attachment"); + return _files.ListFiles(path) .ToDictionary(Path.GetFileNameWithoutExtension, x => $"{BaseURL}/api/shows/{slug}/fonts/{Path.GetFileName(x)}"); } @@ -385,64 +388,43 @@ namespace Kyoo.Api [HttpGet("{showSlug}/font/{slug}")] [HttpGet("{showSlug}/fonts/{slug}")] [Authorize(Policy = "Read")] - public async Task GetFont(string showSlug, string slug) + public async Task GetFont(string showSlug, string slug) { string path = (await _libraryManager.GetShow(showSlug))?.Path; if (path == null) return NotFound(); - string fontPath = Path.Combine(path, "Subtitles", "fonts", slug); - if (!System.IO.File.Exists(fontPath)) - return NotFound(); - - if (_provider == null) - _provider = new FileExtensionContentTypeProvider(); - _provider.TryGetContentType(path, out string contentType); - return PhysicalFile(fontPath, contentType ?? "application/x-font-ttf"); + path = Path.Combine(_files.GetExtraDirectory(path), "Attachment", slug); + return _files.FileResult(path); } [HttpGet("{slug}/poster")] [Authorize(Policy = "Read")] - public async Task GetPoster(string slug) + public async Task GetPoster(string slug) { - string path = (await _libraryManager.GetShow(slug))?.Path; - if (path == null) + Show show = await _libraryManager.GetShow(slug); + if (show == null) return NotFound(); - - string poster = Path.Combine(path, "poster.jpg"); - - if (System.IO.File.Exists(poster)) - return new PhysicalFileResult(Path.GetFullPath(poster), "image/jpg"); - return NotFound(); + return _files.FileResult(await _thumbs.GetShowPoster(show)); } [HttpGet("{slug}/logo")] [Authorize(Policy="Read")] public async Task GetLogo(string slug) { - string path = (await _libraryManager.GetShow(slug))?.Path; - if (path == null) + Show show = await _libraryManager.GetShow(slug); + if (show == null) return NotFound(); - - string logo = Path.Combine(path, "logo.png"); - - if (System.IO.File.Exists(logo)) - return new PhysicalFileResult(Path.GetFullPath(logo), "image/png"); - return NotFound(); + return _files.FileResult(await _thumbs.GetShowLogo(show)); } [HttpGet("{slug}/backdrop")] [Authorize(Policy="Read")] public async Task GetBackdrop(string slug) { - string path = (await _libraryManager.GetShow(slug))?.Path; - if (path == null) + Show show = await _libraryManager.GetShow(slug); + if (show == null) return NotFound(); - - string thumb = Path.Combine(path, "backdrop.jpg"); - - if (System.IO.File.Exists(thumb)) - return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg"); - return NotFound(); + return _files.FileResult(await _thumbs.GetShowBackdrop(show)); } } } diff --git a/Kyoo/Views/API/StudioApi.cs b/Kyoo/Views/API/StudioApi.cs index e244575d..a0cf872e 100644 --- a/Kyoo/Views/API/StudioApi.cs +++ b/Kyoo/Views/API/StudioApi.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; -using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; From 1ad84174f2a6e088a57b4cc6c0f3e58454e43357 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 24 Mar 2021 00:02:27 +0100 Subject: [PATCH 52/54] Cleaning up path handling in extractors --- Kyoo.Common/Controllers/IFileManager.cs | 13 +++++-- Kyoo.Common/Controllers/ITranscoder.cs | 2 +- Kyoo/Controllers/FileManager.cs | 45 ++++++++++++++++++++----- Kyoo/Controllers/ThumbnailsManager.cs | 38 ++++++--------------- Kyoo/Controllers/Transcoder.cs | 8 ++--- Kyoo/Tasks/Crawler.cs | 2 +- Kyoo/Tasks/ExtractMetadata.cs | 2 +- Kyoo/Views/API/ShowApi.cs | 14 ++++---- transcoder | 2 +- 9 files changed, 71 insertions(+), 55 deletions(-) diff --git a/Kyoo.Common/Controllers/IFileManager.cs b/Kyoo.Common/Controllers/IFileManager.cs index aa845f82..03d13d5d 100644 --- a/Kyoo.Common/Controllers/IFileManager.cs +++ b/Kyoo.Common/Controllers/IFileManager.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using JetBrains.Annotations; +using Kyoo.Models; using Microsoft.AspNetCore.Mvc; namespace Kyoo.Controllers @@ -11,11 +13,16 @@ namespace Kyoo.Controllers public StreamReader GetReader([NotNull] string path); - public ICollection ListFiles([NotNull] string path); - // TODO implement a List for directorys, a Exist to check existance and all. + public Task> ListFiles([NotNull] string path); + + public Task Exists([NotNull] string path); // TODO replace every use of System.IO with this to allow custom paths (like uptobox://path) // TODO find a way to handle Transmux/Transcode with this system. - public string GetExtraDirectory(string showPath); + public string GetExtraDirectory(Show show); + + public string GetExtraDirectory(Season season); + + public string GetExtraDirectory(Episode episode); } } \ No newline at end of file diff --git a/Kyoo.Common/Controllers/ITranscoder.cs b/Kyoo.Common/Controllers/ITranscoder.cs index afaa524f..4fc109d1 100644 --- a/Kyoo.Common/Controllers/ITranscoder.cs +++ b/Kyoo.Common/Controllers/ITranscoder.cs @@ -5,7 +5,7 @@ namespace Kyoo.Controllers { public interface ITranscoder { - Task ExtractInfos(string path, bool reextract); + Task ExtractInfos(Episode episode, bool reextract); Task Transmux(Episode episode); Task Transcode(Episode episode); } diff --git a/Kyoo/Controllers/FileManager.cs b/Kyoo/Controllers/FileManager.cs index a9e52478..71dfbb13 100644 --- a/Kyoo/Controllers/FileManager.cs +++ b/Kyoo/Controllers/FileManager.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; +using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; @@ -16,6 +18,8 @@ namespace Kyoo.Controllers { _provider = new FileExtensionContentTypeProvider(); _provider.Mappings[".mkv"] = "video/x-matroska"; + _provider.Mappings[".ass"] = "text/x-ssa"; + _provider.Mappings[".srt"] = "application/x-subrip"; } if (_provider.TryGetContentType(path, out string contentType)) @@ -23,6 +27,7 @@ namespace Kyoo.Controllers throw new NotImplementedException($"Can't get the content type of the file at: {path}"); } + // TODO add a way to force content type public IActionResult FileResult(string path, bool range) { if (path == null) @@ -42,18 +47,40 @@ namespace Kyoo.Controllers return new StreamReader(path); } - public string GetExtraDirectory(string showPath) - { - string path = Path.Combine(showPath, "Extra"); - Directory.CreateDirectory(path); - return path; - } - - public ICollection ListFiles(string path) + public Task> ListFiles(string path) { if (path == null) throw new ArgumentNullException(nameof(path)); - return Directory.GetFiles(path); + return Task.FromResult>(Directory.GetFiles(path)); + } + + public Task Exists(string path) + { + return Task.FromResult(File.Exists(path)); + } + + public string GetExtraDirectory(Show show) + { + string path = Path.Combine(show.Path, "Extra"); + Directory.CreateDirectory(path); + return path; + } + + public string GetExtraDirectory(Season season) + { + if (season.Show == null) + throw new NotImplementedException("Can't get season's extra directory when season.Show == null."); + // TODO use a season.Path here. + string path = Path.Combine(season.Show.Path, "Extra"); + Directory.CreateDirectory(path); + return path; + } + + public string GetExtraDirectory(Episode episode) + { + string path = Path.Combine(Path.GetDirectoryName(episode.Path)!, "Extra"); + Directory.CreateDirectory(path); + return path; } } } \ No newline at end of file diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index b2dc08c7..04e01ff4 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -38,25 +38,21 @@ namespace Kyoo.Controllers public async Task Validate(Show show, bool alwaysDownload) { - if (show?.Path == null) - return default; - string basePath = _files.GetExtraDirectory(show.Path); - if (show.Poster != null) { - string posterPath = Path.Combine(basePath, "poster.jpg"); + string posterPath = await GetShowPoster(show); if (alwaysDownload || !File.Exists(posterPath)) await DownloadImage(show.Poster, posterPath, $"The poster of {show.Title}"); } if (show.Logo != null) { - string logoPath = Path.Combine(basePath, "logo.png"); + string logoPath = await GetShowLogo(show); if (alwaysDownload || !File.Exists(logoPath)) await DownloadImage(show.Logo, logoPath, $"The logo of {show.Title}"); } if (show.Backdrop != null) { - string backdropPath = Path.Combine(basePath, "backdrop.jpg"); + string backdropPath = await GetShowBackdrop(show); if (alwaysDownload || !File.Exists(backdropPath)) await DownloadImage(show.Backdrop, backdropPath, $"The backdrop of {show.Title}"); } @@ -88,8 +84,7 @@ namespace Kyoo.Controllers if (season.Poster != null) { - string basePath = _files.GetExtraDirectory(season.Show.Path); - string localPath = Path.Combine(basePath, $"season-{season.SeasonNumber}.jpg"); + string localPath = await GetSeasonPoster(season); if (alwaysDownload || !File.Exists(localPath)) await DownloadImage(season.Poster, localPath, $"The poster of {season.Show.Title}'s season {season.SeasonNumber}"); } @@ -103,10 +98,7 @@ namespace Kyoo.Controllers if (episode.Thumb != null) { - string localPath = Path.Combine( - _files.GetExtraDirectory(Path.GetDirectoryName(episode.Path)), - "Thumbnails", - $"{Path.GetFileNameWithoutExtension(episode.Path)}.jpg"); + string localPath = await GetEpisodeThumb(episode); if (alwaysDownload || !File.Exists(localPath)) await DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}"); } @@ -131,44 +123,36 @@ namespace Kyoo.Controllers { if (show?.Path == null) throw new ArgumentNullException(nameof(show)); - return Task.FromResult(Path.Combine(_files.GetExtraDirectory(show.Path), "backdrop.jpg")); + return Task.FromResult(Path.Combine(_files.GetExtraDirectory(show), "backdrop.jpg")); } public Task GetShowLogo(Show show) { if (show?.Path == null) throw new ArgumentNullException(nameof(show)); - return Task.FromResult(Path.Combine(_files.GetExtraDirectory(show.Path), "logo.png")); + return Task.FromResult(Path.Combine(_files.GetExtraDirectory(show), "logo.png")); } public Task GetShowPoster(Show show) { if (show?.Path == null) throw new ArgumentNullException(nameof(show)); - return Task.FromResult(Path.Combine(_files.GetExtraDirectory(show.Path), "poster.jpg")); + return Task.FromResult(Path.Combine(_files.GetExtraDirectory(show), "poster.jpg")); } public Task GetSeasonPoster(Season season) { if (season == null) throw new ArgumentNullException(nameof(season)); - // TODO Use a season.Path (for season's folder) - string path = season.Show.Poster; - if (path == null) - return Task.FromResult(null); - - string thumb = Path.Combine(_files.GetExtraDirectory(path), $"season-{season.SeasonNumber}.jpg"); - return Task.FromResult(File.Exists(thumb) ? Path.GetFullPath(thumb) : null); + return Task.FromResult(Path.Combine(_files.GetExtraDirectory(season), $"season-{season.SeasonNumber}.jpg")); } public Task GetEpisodeThumb(Episode episode) { - string path = episode.Path; - // TODO use show's path for get extra directory. If seasons folder are used, episodes may not be directly in the show folder. return Task.FromResult(Path.Combine( - _files.GetExtraDirectory(Path.GetDirectoryName(path)), + _files.GetExtraDirectory(episode), "Thumbnails", - $"{Path.GetFileNameWithoutExtension(path)}.jpg")); + $"{Path.GetFileNameWithoutExtension(episode.Path)}.jpg")); } public Task GetPeoplePoster(People people) diff --git a/Kyoo/Controllers/Transcoder.cs b/Kyoo/Controllers/Transcoder.cs index 26a05e0e..898f7ba4 100644 --- a/Kyoo/Controllers/Transcoder.cs +++ b/Kyoo/Controllers/Transcoder.cs @@ -85,15 +85,13 @@ namespace Kyoo.Controllers throw new BadTranscoderException(); } - public Task ExtractInfos(string path, bool reextract) + public Task ExtractInfos(Episode episode, bool reextract) { - string dir = _files.GetExtraDirectory(path); + string dir = _files.GetExtraDirectory(episode); if (dir == null) throw new ArgumentException("Invalid path."); - // TODO invalid path here. - dir = Path.Combine(dir, "Extra"); return Task.Factory.StartNew( - () => TranscoderAPI.ExtractInfos(path, dir, reextract), + () => TranscoderAPI.ExtractInfos(episode.Path, dir, reextract), TaskCreationOptions.LongRunning); } diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 853ea5a9..71da1381 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -359,7 +359,7 @@ namespace Kyoo.Controllers private async Task> GetTracks(Episode episode) { - episode.Tracks = (await _transcoder.ExtractInfos(episode.Path, false)) + episode.Tracks = (await _transcoder.ExtractInfos(episode, false)) .Where(x => x.Type != StreamType.Attachment) .ToArray(); return episode.Tracks; diff --git a/Kyoo/Tasks/ExtractMetadata.cs b/Kyoo/Tasks/ExtractMetadata.cs index a36659f4..73ed031b 100644 --- a/Kyoo/Tasks/ExtractMetadata.cs +++ b/Kyoo/Tasks/ExtractMetadata.cs @@ -101,7 +101,7 @@ namespace Kyoo.Tasks if (subs) { await _library.Load(episode, x => x.Tracks); - episode.Tracks = (await _transcoder!.ExtractInfos(episode.Path, true)) + episode.Tracks = (await _transcoder!.ExtractInfos(episode, true)) .Where(x => x.Type != StreamType.Attachment) .Concat(episode.Tracks.Where(x => x.IsExternal)) .ToList(); diff --git a/Kyoo/Views/API/ShowApi.cs b/Kyoo/Views/API/ShowApi.cs index 5ab63a00..703f8c45 100644 --- a/Kyoo/Views/API/ShowApi.cs +++ b/Kyoo/Views/API/ShowApi.cs @@ -376,11 +376,11 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task>> GetFonts(string slug) { - string path = (await _libraryManager.GetShow(slug))?.Path; - if (path == null) + Show show = await _libraryManager.GetShow(slug); + if (show == null) return NotFound(); - path = Path.Combine(_files.GetExtraDirectory(path), "Attachment"); - return _files.ListFiles(path) + string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments"); + return (await _files.ListFiles(path)) .ToDictionary(Path.GetFileNameWithoutExtension, x => $"{BaseURL}/api/shows/{slug}/fonts/{Path.GetFileName(x)}"); } @@ -390,10 +390,10 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task GetFont(string showSlug, string slug) { - string path = (await _libraryManager.GetShow(showSlug))?.Path; - if (path == null) + Show show = await _libraryManager.GetShow(showSlug); + if (show == null) return NotFound(); - path = Path.Combine(_files.GetExtraDirectory(path), "Attachment", slug); + string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments", slug); return _files.FileResult(path); } diff --git a/transcoder b/transcoder index 9acd635a..1902defd 160000 --- a/transcoder +++ b/transcoder @@ -1 +1 @@ -Subproject commit 9acd635aca92ad81f1de562e34b2c7c270bade29 +Subproject commit 1902defd32fa98227acad02dabe7f90ee546ec5b From cb2c60c9ad82c47cd6fd5f3ad15b0600213e7e0c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 24 Mar 2021 01:46:00 +0100 Subject: [PATCH 53/54] Fixing people & epiosode thumbnails --- Kyoo/Controllers/ThumbnailsManager.cs | 13 ++++++++----- Kyoo/Views/WebClient | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 04e01ff4..3398f115 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -21,6 +21,8 @@ namespace Kyoo.Controllers _files = files; _peoplePath = Path.GetFullPath(configuration.GetValue("peoplePath")); _providerPath = Path.GetFullPath(configuration.GetValue("providerPath")); + Directory.CreateDirectory(_peoplePath); + Directory.CreateDirectory(_providerPath); } private static async Task DownloadImage(string url, string localPath, string what) @@ -149,10 +151,9 @@ namespace Kyoo.Controllers public Task GetEpisodeThumb(Episode episode) { - return Task.FromResult(Path.Combine( - _files.GetExtraDirectory(episode), - "Thumbnails", - $"{Path.GetFileNameWithoutExtension(episode.Path)}.jpg")); + string dir = Path.Combine(_files.GetExtraDirectory(episode), "Thumbnails"); + Directory.CreateDirectory(dir); + return Task.FromResult(Path.Combine(dir, $"{Path.GetFileNameWithoutExtension(episode.Path)}.jpg")); } public Task GetPeoplePoster(People people) @@ -160,7 +161,7 @@ namespace Kyoo.Controllers if (people == null) throw new ArgumentNullException(nameof(people)); string thumbPath = Path.GetFullPath(Path.Combine(_peoplePath, $"{people.Slug}.jpg")); - if (!thumbPath.StartsWith(_peoplePath) || File.Exists(thumbPath)) + if (!thumbPath.StartsWith(_peoplePath)) return Task.FromResult(null); return Task.FromResult(thumbPath); } @@ -170,6 +171,8 @@ namespace Kyoo.Controllers if (provider == null) throw new ArgumentNullException(nameof(provider)); string thumbPath = Path.GetFullPath(Path.Combine(_providerPath, $"{provider.Slug}.jpg")); + if (!thumbPath.StartsWith(_providerPath)) + return Task.FromResult(null); return Task.FromResult(thumbPath.StartsWith(_providerPath) ? thumbPath : null); } } diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index a8a576bd..ab52f039 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit a8a576bdd9e0c69fbae8e5e4f392c112fe3af8a2 +Subproject commit ab52f039021928cab9f6ed8c17a0488ca198ef74 From f60f622e1d93c852131c04bb9b737de8411fbb63 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 24 Mar 2021 18:12:03 +0100 Subject: [PATCH 54/54] Cleaning up the thumnail's manager --- Kyoo.Common/Controllers/IFileManager.cs | 1 - Kyoo.Common/Controllers/IThumbnailsManager.cs | 10 ++--- Kyoo/Controllers/ThumbnailsManager.cs | 45 +++++++------------ 3 files changed, 21 insertions(+), 35 deletions(-) diff --git a/Kyoo.Common/Controllers/IFileManager.cs b/Kyoo.Common/Controllers/IFileManager.cs index 03d13d5d..cc3c70bb 100644 --- a/Kyoo.Common/Controllers/IFileManager.cs +++ b/Kyoo.Common/Controllers/IFileManager.cs @@ -16,7 +16,6 @@ namespace Kyoo.Controllers public Task> ListFiles([NotNull] string path); public Task Exists([NotNull] string path); - // TODO replace every use of System.IO with this to allow custom paths (like uptobox://path) // TODO find a way to handle Transmux/Transcode with this system. public string GetExtraDirectory(Show show); diff --git a/Kyoo.Common/Controllers/IThumbnailsManager.cs b/Kyoo.Common/Controllers/IThumbnailsManager.cs index 603a0f6f..5d2597cf 100644 --- a/Kyoo.Common/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Common/Controllers/IThumbnailsManager.cs @@ -7,11 +7,11 @@ namespace Kyoo.Controllers { public interface IThumbnailsManager { - Task Validate(Show show, bool alwaysDownload = false); - Task Validate(Season season, bool alwaysDownload = false); - Task Validate(Episode episode, bool alwaysDownload = false); - Task Validate(People actors, bool alwaysDownload = false); - Task Validate(ProviderID actors, bool alwaysDownload = false); + Task Validate(Show show, bool alwaysDownload = false); + Task Validate(Season season, bool alwaysDownload = false); + Task Validate(Episode episode, bool alwaysDownload = false); + Task Validate(People actors, bool alwaysDownload = false); + Task Validate(ProviderID actors, bool alwaysDownload = false); Task GetShowPoster([NotNull] Show show); Task GetShowLogo([NotNull] Show show); diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 3398f115..bc91f1af 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -38,7 +38,7 @@ namespace Kyoo.Controllers } } - public async Task Validate(Show show, bool alwaysDownload) + public async Task Validate(Show show, bool alwaysDownload) { if (show.Poster != null) { @@ -61,11 +61,9 @@ namespace Kyoo.Controllers foreach (PeopleRole role in show.People) await Validate(role.People, alwaysDownload); - - return show; } - public async Task Validate([NotNull] People people, bool alwaysDownload) + public async Task Validate([NotNull] People people, bool alwaysDownload) { if (people == null) throw new ArgumentNullException(nameof(people)); @@ -75,42 +73,32 @@ namespace Kyoo.Controllers Directory.CreateDirectory(root); if (alwaysDownload || !File.Exists(localPath)) await DownloadImage(people.Poster, localPath, $"The profile picture of {people.Name}"); - - return people; } - public async Task Validate(Season season, bool alwaysDownload) + public async Task Validate(Season season, bool alwaysDownload) { - if (season?.Show?.Path == null) - return default; + if (season?.Show?.Path == null || season.Poster == null) + return; - if (season.Poster != null) - { - string localPath = await GetSeasonPoster(season); - if (alwaysDownload || !File.Exists(localPath)) - await DownloadImage(season.Poster, localPath, $"The poster of {season.Show.Title}'s season {season.SeasonNumber}"); - } - return season; + string localPath = await GetSeasonPoster(season); + if (alwaysDownload || !File.Exists(localPath)) + await DownloadImage(season.Poster, localPath, $"The poster of {season.Show.Title}'s season {season.SeasonNumber}"); } - public async Task Validate(Episode episode, bool alwaysDownload) + public async Task Validate(Episode episode, bool alwaysDownload) { - if (episode?.Path == null) - return default; + if (episode?.Path == null || episode.Thumb == null) + return; - if (episode.Thumb != null) - { - string localPath = await GetEpisodeThumb(episode); - if (alwaysDownload || !File.Exists(localPath)) - await DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}"); - } - return episode; + string localPath = await GetEpisodeThumb(episode); + if (alwaysDownload || !File.Exists(localPath)) + await DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}"); } - public async Task Validate(ProviderID provider, bool alwaysDownload) + public async Task Validate(ProviderID provider, bool alwaysDownload) { if (provider.Logo == null) - return provider; + return; string root = _config.GetValue("providerPath"); string localPath = Path.Combine(root, provider.Slug + ".jpg"); @@ -118,7 +106,6 @@ namespace Kyoo.Controllers Directory.CreateDirectory(root); if (alwaysDownload || !File.Exists(localPath)) await DownloadImage(provider.Logo, localPath, $"The logo of {provider.Slug}"); - return provider; } public Task GetShowBackdrop(Show show)