diff --git a/Kyoo.CommonAPI/DatabaseContext.cs b/Kyoo.CommonAPI/DatabaseContext.cs index afca55b5..5bf9e87a 100644 --- a/Kyoo.CommonAPI/DatabaseContext.cs +++ b/Kyoo.CommonAPI/DatabaseContext.cs @@ -74,6 +74,14 @@ namespace Kyoo /// Episodes with a watch percentage. See /// public DbSet WatchedEpisodes { get; set; } + + /// + /// The list of library items (shows and collections that are part of a library - or the global one) + /// + /// + /// This set is ready only, on most database this will be a view. + /// + public DbSet LibraryItems { get; set; } /// /// Get all metadataIDs (ExternalIDs) of a given resource. See . @@ -322,6 +330,8 @@ namespace Kyoo modelBuilder.Entity() .Property(x => x.Slug) .ValueGeneratedOnAddOrUpdate(); + + // modelBuilder.Ignore(); } /// diff --git a/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs b/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs index bfa66554..5e81e797 100644 --- a/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs +++ b/Kyoo.Postgresql/Migrations/20210627141941_Triggers.cs @@ -74,6 +74,25 @@ namespace Kyoo.Postgresql.Migrations migrationBuilder.Sql(@" CREATE TRIGGER show_slug_trigger AFTER UPDATE OF slug ON shows FOR EACH ROW EXECUTE PROCEDURE show_slug_update();"); + + + // language=PostgreSQL + migrationBuilder.Sql(@" + CREATE VIEW library_items AS + SELECT s.id, s.slug, s.title, s.overview, s.status, s.start_air, s.end_air, s.poster, CASE + WHEN s.is_movie THEN 'movie'::item_type + ELSE 'show'::item_type + END AS type + FROM shows AS s + WHERE NOT (EXISTS ( + SELECT 1 + FROM link_collection_show AS l + INNER JOIN collections AS c ON l.first_id = c.id + WHERE s.id = l.second_id)) + UNION ALL + SELECT -c0.id, c0.slug, c0.name AS title, c0.overview, 'unknown'::status AS status, + NULL AS start_air, NULL AS end_air, c0.poster, 'collection'::item_type AS type + FROM collections AS c0"); } protected override void Down(MigrationBuilder migrationBuilder) @@ -90,6 +109,8 @@ namespace Kyoo.Postgresql.Migrations migrationBuilder.Sql("DROP TRIGGER episode_slug_trigger ON episodes;"); // language=PostgreSQL migrationBuilder.Sql(@"DROP FUNCTION episode_slug_update;"); + // language=PostgreSQL + migrationBuilder.Sql(@"DROP VIEW library_items;"); } } } \ No newline at end of file diff --git a/Kyoo.Postgresql/PostgresContext.cs b/Kyoo.Postgresql/PostgresContext.cs index f070a805..b0e534ed 100644 --- a/Kyoo.Postgresql/PostgresContext.cs +++ b/Kyoo.Postgresql/PostgresContext.cs @@ -91,6 +91,10 @@ namespace Kyoo.Postgresql modelBuilder.HasPostgresEnum(); modelBuilder.HasPostgresEnum(); + modelBuilder.Entity() + .ToView("library_items") + .HasKey(x => x.ID); + modelBuilder.Entity() .Property(x => x.ExtraData) .HasColumnType("jsonb"); diff --git a/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs b/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs index d6d3ca22..7d3c7c6a 100644 --- a/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs +++ b/Kyoo.SqLite/Migrations/20210626141347_Triggers.cs @@ -61,6 +61,25 @@ namespace Kyoo.SqLite.Migrations END WHERE ShowID = new.ID; END;"); + + + // language=SQLite + migrationBuilder.Sql(@" + CREATE VIEW LibraryItems AS + SELECT s.ID, s.Slug, s.Title, s.Overview, s.Status, s.StartAir, s.EndAir, s.Poster, CASE + WHEN s.IsMovie THEN 1 + ELSE 0 + END AS Type + FROM Shows AS s + WHERE NOT (EXISTS ( + SELECT 1 + FROM 'Link' AS l + INNER JOIN Collections AS c ON l.FirstID = c.ID + WHERE s.ID = l.SecondID)) + UNION ALL + SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 3 AS Status, + NULL AS StartAir, NULL AS EndAir, c0.Poster, 2 AS Type + FROM collections AS c0"); } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Kyoo.SqLite/SqLiteContext.cs b/Kyoo.SqLite/SqLiteContext.cs index 81b1626e..23145cf1 100644 --- a/Kyoo.SqLite/SqLiteContext.cs +++ b/Kyoo.SqLite/SqLiteContext.cs @@ -108,6 +108,10 @@ namespace Kyoo.SqLite modelBuilder.Entity() .Property(x => x.ExtraData) .HasConversion(jsonConvertor); + + modelBuilder.Entity() + .ToView("LibraryItems") + .HasKey(x => x.ID); base.OnModelCreating(modelBuilder); } diff --git a/Kyoo.Tests/Library/RepositoryActivator.cs b/Kyoo.Tests/Library/RepositoryActivator.cs index e7249e3a..bf7b7b41 100644 --- a/Kyoo.Tests/Library/RepositoryActivator.cs +++ b/Kyoo.Tests/Library/RepositoryActivator.cs @@ -30,9 +30,7 @@ namespace Kyoo.Tests ShowRepository show = new(_database, studio, people, genre, provider); SeasonRepository season = new(_database, provider); LibraryItemRepository libraryItem = new(_database, - new Lazy(() => LibraryManager.LibraryRepository), - new Lazy(() => LibraryManager.ShowRepository), - new Lazy(() => LibraryManager.CollectionRepository)); + new Lazy(() => LibraryManager.LibraryRepository)); TrackRepository track = new(_database); EpisodeRepository episode = new(_database, provider, track); diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 703ece0b..37391c5d 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -22,15 +22,7 @@ namespace Kyoo.Controllers /// A lazy loaded library repository to validate queries (check if a library does exist) /// private readonly Lazy _libraries; - /// - /// A lazy loaded show repository to get a show from it's id. - /// - private readonly Lazy _shows; - /// - /// A lazy loaded collection repository to get a collection from it's id. - /// - private readonly Lazy _collections; - + /// protected override Expression> DefaultSort => x => x.Title; @@ -38,60 +30,41 @@ namespace Kyoo.Controllers /// /// Create a new . /// - /// The databse instance + /// The database instance /// A lazy loaded library repository - /// A lazy loaded show repository - /// A lazy loaded collection repository public LibraryItemRepository(DatabaseContext database, - Lazy libraries, - Lazy shows, - Lazy collections) + Lazy libraries) : base(database) { _database = database; _libraries = libraries; - _shows = shows; - _collections = collections; } /// - public override async Task GetOrDefault(int id) + public override Task GetOrDefault(int id) { - return id > 0 - ? new LibraryItem(await _shows.Value.GetOrDefault(id)) - : new LibraryItem(await _collections.Value.GetOrDefault(-id)); + return _database.LibraryItems.FirstOrDefaultAsync(x => x.ID == id); } /// public override Task GetOrDefault(string slug) { - throw new InvalidOperationException("You can't get a library item by a slug."); + return _database.LibraryItems.SingleOrDefaultAsync(x => x.Slug == slug); } - /// - /// Get a basic queryable with the right mapping from shows & collections. - /// Shows contained in a collection are excluded. - /// - private IQueryable ItemsQuery - => _database.Shows - .Where(x => !x.Collections.Any()) - .Select(LibraryItem.FromShow) - .Concat(_database.Collections - .Select(LibraryItem.FromCollection)); - /// public override Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) { - return ApplyFilters(ItemsQuery, where, sort, limit); + return ApplyFilters(_database.LibraryItems, where, sort, limit); } /// public override Task GetCount(Expression> where = null) { - IQueryable query = ItemsQuery; + IQueryable query = _database.LibraryItems; if (where != null) query = query.Where(where); return query.CountAsync(); @@ -100,7 +73,7 @@ namespace Kyoo.Controllers /// public override async Task> Search(string query) { - return await ItemsQuery + return await _database.LibraryItems .Where(_database.Like(x => x.Title, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) @@ -109,7 +82,6 @@ namespace Kyoo.Controllers /// public override Task Create(LibraryItem obj) => throw new InvalidOperationException(); - /// public override Task CreateIfNotExists(LibraryItem obj) => throw new InvalidOperationException(); ///