diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs index e17d68e2..59cd116d 100644 --- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The repository that handle libraries items (a wrapper around shows and collections). /// - IRepository LibraryItems { get; } + IRepository LibraryItems { get; } /// /// The repository that handle collections. diff --git a/back/src/Kyoo.Abstractions/Controllers/ISearchManager.cs b/back/src/Kyoo.Abstractions/Controllers/ISearchManager.cs index 695b44f9..71a4656d 100644 --- a/back/src/Kyoo.Abstractions/Controllers/ISearchManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/ISearchManager.cs @@ -35,10 +35,10 @@ public interface ISearchManager /// How pagination should be done (where to start and how many to return) /// The related fields to include. /// A list of resources that match every filters - public Task.SearchResult> SearchItems(string? query, - Sort sortBy, + public Task.SearchResult> SearchItems(string? query, + Sort sortBy, SearchPagination pagination, - Include? include = default); + Include? include = default); /// /// Search for movies. diff --git a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs index 61e0867f..14fc8aa0 100644 --- a/back/src/Kyoo.Abstractions/Models/LibraryItem.cs +++ b/back/src/Kyoo.Abstractions/Models/LibraryItem.cs @@ -30,138 +30,20 @@ namespace Kyoo.Abstractions.Models public enum ItemKind { /// - /// The is a . + /// The is a . /// Show, /// - /// The is a Movie. + /// The is a Movie. /// Movie, /// - /// The is a . + /// The is a . /// Collection } - public class LibraryItem : IResource, IThumbnails, IMetadata, IAddedDate - { - /// - public int Id { get; set; } - - /// - [MaxLength(256)] - public string Slug { get; set; } - - /// - /// The title of this show. - /// - public string Name { get; set; } - - /// - /// A catchphrase for this movie. - /// - public string? Tagline { get; set; } - - /// - /// The list of alternative titles of this show. - /// - public string[] Aliases { get; set; } = Array.Empty(); - - /// - /// The path of the movie video file. - /// - public string? Path { get; set; } - - /// - /// The summary of this show. - /// - public string? Overview { get; set; } - - /// - /// A list of tags that match this movie. - /// - public string[] Tags { get; set; } = Array.Empty(); - - /// - /// The list of genres (themes) this show has. - /// - public Genre[] Genres { get; set; } = Array.Empty(); - - /// - /// Is this show airing, not aired yet or finished? - /// - public Status Status { get; set; } - - /// - /// How well this item is rated? (from 0 to 100). - /// - public int Rating { get; set; } - - /// - /// How long is this movie? (in minutes) - /// - public int? Runtime { get; set; } - - /// - /// The date this show started airing. It can be null if this is unknown. - /// - public DateTime? StartAir { get; set; } - - /// - /// The date this show finished airing. - /// It can also be null if this is unknown. - /// - public DateTime? EndAir { get; set; } - - /// - /// The date this movie aired. - /// - public DateTime? AirDate { get; set; } - - /// - public DateTime AddedDate { get; set; } - - /// - public Image? Poster { get; set; } - - /// - public Image? Thumbnail { get; set; } - - /// - public Image? Logo { get; set; } - - /// - /// A video of a few minutes that tease the content. - /// - public string? Trailer { get; set; } - - /// - /// Is the item a collection, a movie or a show? - /// - public ItemKind Kind { get; set; } - - /// - public Dictionary ExternalId { get; set; } = new(); - - /// - /// Links to watch this movie. - /// - public VideoLinks? Links => Kind == ItemKind.Movie ? new() - { - Direct = $"/video/movie/{Slug}/direct", - Hls = $"/video/movie/{Slug}/master.m3u8", - } - : null; - - public LibraryItem() { } - - [JsonConstructor] - public LibraryItem(string name) - { - Slug = Utility.ToSlug(name); - Name = name; - } - } + public interface ILibraryItem : IResource, IThumbnails, IMetadata, IAddedDate { } } diff --git a/back/src/Kyoo.Abstractions/Models/News.cs b/back/src/Kyoo.Abstractions/Models/News.cs index ca79c732..9ecb8f99 100644 --- a/back/src/Kyoo.Abstractions/Models/News.cs +++ b/back/src/Kyoo.Abstractions/Models/News.cs @@ -28,12 +28,12 @@ namespace Kyoo.Abstractions.Models public enum NewsKind { /// - /// The is an . + /// The is an . /// Episode, /// - /// The is a Movie. + /// The is a Movie. /// Movie, } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs index bb679b53..f0f149aa 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs @@ -28,7 +28,7 @@ namespace Kyoo.Abstractions.Models /// /// A class representing collections of . /// - public class Collection : IResource, IMetadata, IThumbnails, IAddedDate + public class Collection : IResource, IMetadata, IThumbnails, IAddedDate, ILibraryItem { /// public int Id { get; set; } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index 3e98ab3e..92babc84 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -28,7 +28,7 @@ namespace Kyoo.Abstractions.Models /// /// A series or a movie. /// - public class Movie : IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate + public class Movie : IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem { /// public int Id { get; set; } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs index eabaa5a9..ea9127ef 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -30,7 +30,7 @@ namespace Kyoo.Abstractions.Models /// /// A series or a movie. /// - public class Show : IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate + public class Show : IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem { /// public int Id { get; set; } diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs index a4082f51..17cfb63d 100644 --- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -30,7 +30,7 @@ namespace Kyoo.Core.Controllers private readonly IBaseRepository[] _repositories; public LibraryManager( - IRepository libraryItemRepository, + IRepository libraryItemRepository, IRepository collectionRepository, IRepository movieRepository, IRepository showRepository, @@ -65,7 +65,7 @@ namespace Kyoo.Core.Controllers } /// - public IRepository LibraryItems { get; } + public IRepository LibraryItems { get; } /// public IRepository Collections { get; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index f3b3dd12..f7e70dd1 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -36,13 +36,11 @@ namespace Kyoo.Core.Controllers /// /// A local repository to handle library items. /// - public class LibraryItemRepository : IRepository + public class LibraryItemRepository : IRepository { private readonly DbConnection _database; - protected Sort DefaultSort => new Sort.By(x => x.Name); - - public Type RepositoryType => typeof(LibraryItem); + public Type RepositoryType => typeof(ILibraryItem); public LibraryItemRepository(DbConnection database) { @@ -50,45 +48,45 @@ namespace Kyoo.Core.Controllers } /// - public virtual async Task Get(int id, Include? include = default) + public virtual async Task Get(int id, Include? include = default) { - LibraryItem? ret = await GetOrDefault(id, include); + ILibraryItem? ret = await GetOrDefault(id, include); if (ret == null) - throw new ItemNotFoundException($"No {nameof(LibraryItem)} found with the id {id}"); + throw new ItemNotFoundException($"No {nameof(ILibraryItem)} found with the id {id}"); return ret; } /// - public virtual async Task Get(string slug, Include? include = default) + public virtual async Task Get(string slug, Include? include = default) { - LibraryItem? ret = await GetOrDefault(slug, include); + ILibraryItem? ret = await GetOrDefault(slug, include); if (ret == null) - throw new ItemNotFoundException($"No {nameof(LibraryItem)} found with the slug {slug}"); + throw new ItemNotFoundException($"No {nameof(ILibraryItem)} found with the slug {slug}"); return ret; } /// - public virtual async Task Get( - Expression> where, - Include? include = default) + public virtual async Task Get( + Expression> where, + Include? include = default) { - LibraryItem? ret = await GetOrDefault(where, include: include); + ILibraryItem? ret = await GetOrDefault(where, include: include); if (ret == null) - throw new ItemNotFoundException($"No {nameof(LibraryItem)} found with the given predicate."); + throw new ItemNotFoundException($"No {nameof(ILibraryItem)} found with the given predicate."); return ret; } - public Task GetOrDefault(int id, Include? include = null) + public Task GetOrDefault(int id, Include? include = null) { throw new NotImplementedException(); } - public Task GetOrDefault(string slug, Include? include = null) + public Task GetOrDefault(string slug, Include? include = null) { throw new NotImplementedException(); } - public Task GetOrDefault(Expression> where, Include? include = null, Sort? sortBy = null) + public Task GetOrDefault(Expression> where, Include? include = null, Sort? sortBy = null) { throw new NotImplementedException(); } @@ -106,11 +104,11 @@ namespace Kyoo.Core.Controllers }; } - public async Task> GetAll( - Expression>? where = null, - Sort? sort = null, + public async Task> GetAll( + Expression>? where = null, + Sort? sort = null, Pagination? limit = null, - Include? include = null) + Include? include = null) { // language=PostgreSQL IDapperSqlCommand query = _database.SqlBuilder($""" @@ -136,7 +134,8 @@ namespace Kyoo.Core.Controllers limit {limit.Limit} """).Build(); - var data = await query.QueryAsync(new[] { typeof(Show), typeof(Movie), typeof(Collection), typeof(Studio) }, items => + Type[] types = new[] { typeof(Show), typeof(Movie), typeof(Collection), typeof(Studio) }; + IEnumerable data = await query.QueryAsync(types, items => { var studio = items[3] as Studio; if (items[0] is Show show && show.Id != 0) @@ -147,44 +146,26 @@ namespace Kyoo.Core.Controllers return collection; throw new InvalidDataException(); }); - - // await using DbDataReader reader = await _database.ExecuteReaderAsync(sql); - // int kindOrdinal = reader.GetOrdinal("kind"); - // var showParser = reader.GetRowParser(typeof(Show)); - // var movieParser = reader.GetRowParser(typeof(Movie)); - // var collectionParser = reader.GetRowParser(typeof(Collection)); - // - // while (await reader.ReadAsync()) - // { - // ItemKind type = await reader.GetFieldValueAsync(kindOrdinal); - // ret.Add(type switch - // { - // ItemKind.Show => showParser(reader), - // ItemKind.Movie => movieParser(reader), - // ItemKind.Collection => collectionParser(reader), - // _ => throw new InvalidDataException(), - // }); - // } - throw new NotImplementedException(); - // return ret; + return data.ToList(); } - public Task GetCount(Expression>? where = null) + public Task GetCount(Expression>? where = null) { throw new NotImplementedException(); } - public Task> FromIds(IList ids, Include? include = null) + public Task> FromIds(IList ids, Include? include = null) { throw new NotImplementedException(); } - public Task DeleteAll(Expression> where) + public Task DeleteAll(Expression> where) { throw new NotImplementedException(); } + /// - public async Task> Search(string query, Include? include = default) + public async Task> Search(string query, Include? include = default) { throw new NotImplementedException(); // return await Sort( @@ -195,12 +176,12 @@ namespace Kyoo.Core.Controllers // .ToListAsync(); } - public async Task> GetAllOfCollection( + public async Task> GetAllOfCollection( Expression> selector, - Expression>? where = null, - Sort? sort = default, + Expression>? where = null, + Sort? sort = default, Pagination? limit = default, - Include? include = default) + Include? include = default) { throw new NotImplementedException(); // return await ApplyFilters( @@ -220,19 +201,19 @@ namespace Kyoo.Core.Controllers } /// - public Task Create(LibraryItem obj) + public Task Create(ILibraryItem obj) => throw new InvalidOperationException(); /// - public Task CreateIfNotExists(LibraryItem obj) + public Task CreateIfNotExists(ILibraryItem obj) => throw new InvalidOperationException(); /// - public Task Edit(LibraryItem edited) + public Task Edit(ILibraryItem edited) => throw new InvalidOperationException(); /// - public Task Patch(int id, Func> patch) + public Task Patch(int id, Func> patch) => throw new InvalidOperationException(); /// @@ -244,7 +225,7 @@ namespace Kyoo.Core.Controllers => throw new InvalidOperationException(); /// - public Task Delete(LibraryItem obj) + public Task Delete(ILibraryItem obj) => throw new InvalidOperationException(); } } diff --git a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs index a334e9ba..6e22c7dc 100644 --- a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -119,7 +119,6 @@ namespace Kyoo.Core.Controllers { string directory = item switch { - LibraryItem litem => Path.Combine("./metadata", litem.Kind.ToString().ToLowerInvariant(), litem.Slug), IResource res => Path.Combine("./metadata", item.GetType().Name.ToLowerInvariant(), res.Slug), _ => Path.Combine("./metadata", typeof(T).Name.ToLowerInvariant()) }; diff --git a/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs b/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs index ef7bbf10..c4289488 100644 --- a/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs @@ -138,16 +138,16 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetItems(Identifier identifier, - [FromQuery] Sort sortBy, + public async Task>> GetItems(Identifier identifier, + [FromQuery] Sort sortBy, [FromQuery] Dictionary where, [FromQuery] Pagination pagination, - [FromQuery] Include? fields) + [FromQuery] Include? fields) { - ICollection resources = await _items.GetAllOfCollection( + ICollection resources = await _items.GetAllOfCollection( identifier.IsSame(), - ApiHelper.ParseWhere(where), - sortBy == new Sort.Default() ? new Sort.By(x => x.AirDate) : sortBy, + ApiHelper.ParseWhere(where), + sortBy == new Sort.Default() ? new Sort.By(nameof(Movie.AirDate)) : sortBy, pagination, fields ); diff --git a/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs b/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs index 79ef417f..cc1d9c0c 100644 --- a/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs @@ -35,12 +35,12 @@ namespace Kyoo.Core.Api [ResourceView] [PartialPermission("LibraryItem")] [ApiDefinition("Items", Group = ResourcesGroup)] - public class LibraryItemApi : CrudThumbsApi + public class LibraryItemApi : CrudThumbsApi { /// /// The library item repository used to modify or retrieve information in the data store. /// - private readonly IRepository _libraryItems; + private readonly IRepository _libraryItems; /// /// Create a new . @@ -49,7 +49,7 @@ namespace Kyoo.Core.Api /// The library item repository used to modify or retrieve information in the data store. /// /// Thumbnail manager to retrieve images. - public LibraryItemApi(IRepository libraryItems, IThumbnailsManager thumbs) + public LibraryItemApi(IRepository libraryItems, IThumbnailsManager thumbs) : base(libraryItems, thumbs) { _libraryItems = libraryItems; diff --git a/back/src/Kyoo.Core/Views/Resources/SearchApi.cs b/back/src/Kyoo.Core/Views/Resources/SearchApi.cs index 59c4a081..93c21fff 100644 --- a/back/src/Kyoo.Core/Views/Resources/SearchApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/SearchApi.cs @@ -135,14 +135,14 @@ namespace Kyoo.Core.Api /// A list of items found for the specified query. [HttpGet("items")] [HttpGet("item", Order = AlternativeRoute)] - [Permission(nameof(LibraryItem), Kind.Read)] + [Permission(nameof(ILibraryItem), Kind.Read)] [ApiDefinition("Item")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> SearchItems( + public async Task> SearchItems( [FromQuery] string? q, - [FromQuery] Sort sortBy, + [FromQuery] Sort sortBy, [FromQuery] SearchPagination pagination, - [FromQuery] Include fields) + [FromQuery] Include fields) { return SearchPage(await _searchManager.SearchItems(q, sortBy, pagination, fields)); } diff --git a/back/src/Kyoo.Meilisearch/MeilisearchModule.cs b/back/src/Kyoo.Meilisearch/MeilisearchModule.cs index b3fc2d91..1a4a5042 100644 --- a/back/src/Kyoo.Meilisearch/MeilisearchModule.cs +++ b/back/src/Kyoo.Meilisearch/MeilisearchModule.cs @@ -41,32 +41,32 @@ namespace Kyoo.Meiliseach { SearchableAttributes = new[] { - CamelCase.ConvertName(nameof(LibraryItem.Name)), - CamelCase.ConvertName(nameof(LibraryItem.Slug)), - CamelCase.ConvertName(nameof(LibraryItem.Aliases)), - CamelCase.ConvertName(nameof(LibraryItem.Path)), - CamelCase.ConvertName(nameof(LibraryItem.Tags)), - CamelCase.ConvertName(nameof(LibraryItem.Overview)), + CamelCase.ConvertName(nameof(Movie.Name)), + CamelCase.ConvertName(nameof(Movie.Slug)), + CamelCase.ConvertName(nameof(Movie.Aliases)), + CamelCase.ConvertName(nameof(Movie.Path)), + CamelCase.ConvertName(nameof(Movie.Tags)), + CamelCase.ConvertName(nameof(Movie.Overview)), }, FilterableAttributes = new[] { - CamelCase.ConvertName(nameof(LibraryItem.Genres)), - CamelCase.ConvertName(nameof(LibraryItem.Status)), - CamelCase.ConvertName(nameof(LibraryItem.AirDate)), + CamelCase.ConvertName(nameof(Movie.Genres)), + CamelCase.ConvertName(nameof(Movie.Status)), + CamelCase.ConvertName(nameof(Movie.AirDate)), CamelCase.ConvertName(nameof(Movie.StudioId)), - CamelCase.ConvertName(nameof(LibraryItem.Kind)), + "kind" }, SortableAttributes = new[] { - CamelCase.ConvertName(nameof(LibraryItem.AirDate)), - CamelCase.ConvertName(nameof(LibraryItem.AddedDate)), - CamelCase.ConvertName(nameof(LibraryItem.Rating)), - CamelCase.ConvertName(nameof(LibraryItem.Runtime)), + CamelCase.ConvertName(nameof(Movie.AirDate)), + CamelCase.ConvertName(nameof(Movie.AddedDate)), + CamelCase.ConvertName(nameof(Movie.Rating)), + CamelCase.ConvertName(nameof(Movie.Runtime)), }, DisplayedAttributes = new[] { - CamelCase.ConvertName(nameof(LibraryItem.Id)), - CamelCase.ConvertName(nameof(LibraryItem.Kind)), + CamelCase.ConvertName(nameof(Movie.Id)), + "kind" }, RankingRules = new[] { @@ -76,10 +76,9 @@ namespace Kyoo.Meiliseach "attribute", "sort", "exactness", - $"{CamelCase.ConvertName(nameof(LibraryItem.Rating))}:desc", + $"{CamelCase.ConvertName(nameof(Movie.Rating))}:desc", } // TODO: Add stopwords - // TODO: Extend default ranking to add ratings. } }, { diff --git a/back/src/Kyoo.Meilisearch/SearchManager.cs b/back/src/Kyoo.Meilisearch/SearchManager.cs index a3ec477d..9ebed39c 100644 --- a/back/src/Kyoo.Meilisearch/SearchManager.cs +++ b/back/src/Kyoo.Meilisearch/SearchManager.cs @@ -73,10 +73,10 @@ public class SearchManager : ISearchManager }; } - public async Task.SearchResult> SearchItems(string? query, - Sort sortBy, + public async Task.SearchResult> SearchItems(string? query, + Sort sortBy, SearchPagination pagination, - Include? include = default) + Include? include = default) { // TODO: add filters and facets ISearchable res = await _client.Index("items").SearchAsync(query, new SearchQuery() @@ -96,7 +96,7 @@ public class SearchManager : ISearchManager _ => throw new InvalidOperationException("An unknown item kind was found in meilisearch"), }).ToList(); - return new SearchPage.SearchResult + return new SearchPage.SearchResult { Query = query, Items = await _libraryManager.LibraryItems diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index 9509da6b..52a8464d 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -92,14 +92,6 @@ namespace Kyoo.Postgresql /// public DbSet PeopleRoles { 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; } - /// /// The list of new items (episodes and movies). /// @@ -291,7 +283,6 @@ namespace Kyoo.Postgresql .WithMany("Users") .UsingEntity(x => x.ToTable(LinkName())); - _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); @@ -301,7 +292,6 @@ namespace Kyoo.Postgresql _HasMetadata(modelBuilder); _HasMetadata(modelBuilder); - _HasImages(modelBuilder); _HasImages(modelBuilder); _HasImages(modelBuilder); _HasImages(modelBuilder); @@ -310,7 +300,6 @@ namespace Kyoo.Postgresql _HasImages(modelBuilder); _HasImages(modelBuilder); - _HasAddedDate(modelBuilder); _HasAddedDate(modelBuilder); _HasAddedDate(modelBuilder); _HasAddedDate(modelBuilder); @@ -357,8 +346,6 @@ namespace Kyoo.Postgresql modelBuilder.Entity() .Ignore(x => x.Links); - modelBuilder.Entity() - .Ignore(x => x.Links); modelBuilder.Entity() .Ignore(x => x.Links);