From 7810f626c66abe721295d53099a5ac943a18587b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 5 Dec 2023 23:53:27 +0100 Subject: [PATCH] Add watchlist api --- .../Controllers/IWatchStatusRepository.cs | 3 +- .../Models/{News.cs => INews.cs} | 0 .../Kyoo.Abstractions/Models/IWatchlist.cs | 31 +++++++ .../Models/Resources/Episode.cs | 2 +- .../Models/Resources/Movie.cs | 2 +- .../Models/Resources/Show.cs | 2 +- .../Models/Resources/WatchStatus.cs | 3 - .../Repositories/WatchStatusRepository.cs | 58 +++++++++++++- .../Kyoo.Core/Views/Resources/WatchlistApi.cs | 80 +++++++++++++++++++ 9 files changed, 171 insertions(+), 10 deletions(-) rename back/src/Kyoo.Abstractions/Models/{News.cs => INews.cs} (100%) create mode 100644 back/src/Kyoo.Abstractions/Models/IWatchlist.cs create mode 100644 back/src/Kyoo.Core/Views/Resources/WatchlistApi.cs diff --git a/back/src/Kyoo.Abstractions/Controllers/IWatchStatusRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IWatchStatusRepository.cs index ca653c53..7c714322 100644 --- a/back/src/Kyoo.Abstractions/Controllers/IWatchStatusRepository.cs +++ b/back/src/Kyoo.Abstractions/Controllers/IWatchStatusRepository.cs @@ -17,7 +17,6 @@ // along with Kyoo. If not, see . using System; -using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Abstractions.Models; @@ -26,7 +25,7 @@ namespace Kyoo.Abstractions.Controllers; /// /// A local repository to handle watched items /// -public interface IWatchStatusRepository +public interface IWatchStatusRepository : IRepository { // /// // /// The event handler type for all events of this repository. diff --git a/back/src/Kyoo.Abstractions/Models/News.cs b/back/src/Kyoo.Abstractions/Models/INews.cs similarity index 100% rename from back/src/Kyoo.Abstractions/Models/News.cs rename to back/src/Kyoo.Abstractions/Models/INews.cs diff --git a/back/src/Kyoo.Abstractions/Models/IWatchlist.cs b/back/src/Kyoo.Abstractions/Models/IWatchlist.cs new file mode 100644 index 00000000..498e2970 --- /dev/null +++ b/back/src/Kyoo.Abstractions/Models/IWatchlist.cs @@ -0,0 +1,31 @@ +// Kyoo - A portable and vast media library solution. +// Copyright (c) Kyoo. +// +// See AUTHORS.md and LICENSE file in the project root for full license information. +// +// Kyoo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// Kyoo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kyoo. If not, see . + +using Kyoo.Abstractions.Controllers; +using Kyoo.Abstractions.Models.Attributes; + +namespace Kyoo.Abstractions.Models; + +/// +/// A watch list item. +/// +[OneOf(Types = new[] { typeof(Show), typeof(Movie), typeof(Episode) })] +public interface IWatchlist : IResource, IThumbnails, IMetadata, IAddedDate, IQuery +{ + static Sort IQuery.DefaultSort => new Sort.By(nameof(AddedDate), true); +} diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index 1d0a728e..55aae426 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Models /// /// A class to represent a single show's episode. /// - public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, INews + public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, INews, IWatchlist { // Use absolute numbers by default and fallback to season/episodes if it does not exists. public static Sort DefaultSort => new Sort.Conglomerate( diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index 819bc4fd..fff39628 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Models /// /// A series or a movie. /// - public class Movie : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem, INews + public class Movie : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem, INews, IWatchlist { public static Sort DefaultSort => new Sort.By(x => x.Name); diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs index 8c2a6897..bef33e61 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -32,7 +32,7 @@ namespace Kyoo.Abstractions.Models /// /// A series or a movie. /// - public class Show : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem + public class Show : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem, IWatchlist { public static Sort DefaultSort => new Sort.By(x => x.Name); diff --git a/back/src/Kyoo.Abstractions/Models/Resources/WatchStatus.cs b/back/src/Kyoo.Abstractions/Models/Resources/WatchStatus.cs index c6f14bc5..80216f2d 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/WatchStatus.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/WatchStatus.cs @@ -17,9 +17,6 @@ // along with Kyoo. If not, see . using System; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using EntityFrameworkCore.Projectables; using Kyoo.Abstractions.Models.Attributes; namespace Kyoo.Abstractions.Models diff --git a/back/src/Kyoo.Core/Controllers/Repositories/WatchStatusRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/WatchStatusRepository.cs index d27f3e32..a418fe30 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/WatchStatusRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/WatchStatusRepository.cs @@ -17,7 +17,10 @@ // along with Kyoo. If not, see . using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Data.Common; +using System.IO; using System.Linq; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; @@ -28,7 +31,7 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Core.Controllers; -public class WatchStatusRepository : IWatchStatusRepository +public class WatchStatusRepository : DapperRepository, IWatchStatusRepository { /// /// If the watch percent is below this value, don't consider the item started. @@ -55,13 +58,64 @@ public class WatchStatusRepository : IWatchStatusRepository public WatchStatusRepository(DatabaseContext database, IRepository episodes, - IRepository movies) + IRepository movies, + DbConnection db, + SqlVariableContext context) + : base(db, context) { _database = database; _episodes = episodes; _movies = movies; } + // language=PostgreSQL + protected override FormattableString Sql => $""" + select + s.*, + m.*, + e.* + /* includes */ + from ( + select + s.* -- Show as s + from + shows as s + inner join show_watch_status as sw on sw.show_id = s.id + and sw.user_id = [current_user]) as s + full outer join ( + select + m.* -- Movie as m + from + movies as m + inner join movie_watch_status as mw on mw.movie_id = m.id + and mw.user_id = [current_user]) as s) as m + full outer join ( + select + e.* -- Episode as e + from + episode as es + inner join episode_watch_status as ew on ew.episode_id = e.id + and ew.user_id = [current_user])) as e + """; + + protected override Dictionary Config => new() + { + { "s", typeof(Show) }, + { "m", typeof(Movie) }, + { "e", typeof(Episode) }, + }; + + protected override IWatchlist Mapper(List items) + { + if (items[0] is Show show && show.Id != Guid.Empty) + return show; + if (items[1] is Movie movie && movie.Id != Guid.Empty) + return movie; + if (items[2] is Episode episode && episode.Id != Guid.Empty) + return episode; + throw new InvalidDataException(); + } + /// public Task GetMovieStatus(Guid movieId, Guid userId) { diff --git a/back/src/Kyoo.Core/Views/Resources/WatchlistApi.cs b/back/src/Kyoo.Core/Views/Resources/WatchlistApi.cs new file mode 100644 index 00000000..ca17b04b --- /dev/null +++ b/back/src/Kyoo.Core/Views/Resources/WatchlistApi.cs @@ -0,0 +1,80 @@ +// Kyoo - A portable and vast media library solution. +// Copyright (c) Kyoo. +// +// See AUTHORS.md and LICENSE file in the project root for full license information. +// +// Kyoo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// Kyoo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kyoo. If not, see . + +using System.Collections.Generic; +using System.Threading.Tasks; +using Kyoo.Abstractions.Controllers; +using Kyoo.Abstractions.Models; +using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Abstractions.Models.Permissions; +using Kyoo.Abstractions.Models.Utils; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using static Kyoo.Abstractions.Models.Utils.Constants; + +namespace Kyoo.Core.Api +{ + /// + /// List new items added to kyoo. + /// + [Route("watchlist")] + [ApiController] + [PartialPermission("LibraryItem")] + [ApiDefinition("News", Group = ResourcesGroup)] + public class WatchlistApi : BaseApi + { + private readonly IWatchStatusRepository _repository; + + public WatchlistApi(IWatchStatusRepository repository) + { + _repository = repository; + } + + /// + /// Get all + /// + /// + /// Get all resources that match the given filter. + /// + /// Sort information about the query (sort by, sort order). + /// Filter the returned items. + /// How many items per page should be returned, where should the page start... + /// The aditional fields to include in the result. + /// A list of resources that match every filters. + /// Invalid filters or sort information. + [HttpGet] + [PartialPermission(Kind.Read)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] + public async Task>> GetAll( + [FromQuery] Sort sortBy, + [FromQuery] Filter? filter, + [FromQuery] Pagination pagination, + [FromQuery] Include? fields) + { + ICollection resources = await _repository.GetAll( + filter, + sortBy, + fields, + pagination + ); + + return Page(resources, pagination.Limit); + } + } +}