diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs index e1a2f299..ad4e6afe 100644 --- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -33,6 +33,16 @@ namespace Kyoo.Abstractions.Controllers /// IRepository LibraryItems { get; } + /// + /// The repository that handle new items. + /// + IRepository News { get; } + + /// + /// The repository that handle watched items. + /// + IWatchItemsRepository WatchItems { get; } + /// /// The repository that handle collections. /// diff --git a/back/src/Kyoo.Abstractions/Controllers/IWatchItemsRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IWatchItemsRepository.cs new file mode 100644 index 00000000..465bc838 --- /dev/null +++ b/back/src/Kyoo.Abstractions/Controllers/IWatchItemsRepository.cs @@ -0,0 +1,56 @@ +// Kyoo - A portable and vast media library solution. +// Copyright (c) Kyoo. +// +// See AUTHORS.md and LICENSE file in the project root for full license information. +// +// Kyoo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// Kyoo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kyoo. If not, see . + +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Kyoo.Abstractions.Models; + +namespace Kyoo.Abstractions.Controllers; + +/// +/// A local repository to handle watched items +/// +public interface IWatchItemsRepository : IBaseRepository +{ + // /// + // /// The event handler type for all events of this repository. + // /// + // /// The resource created/modified/deleted + // /// A representing the asynchronous operation. + // public delegate Task ResourceEventHandler(T resource); + + /// + /// Get the watch status of a movie + /// + /// The movie selector. + /// The id of the user. + /// The movie's status + Task GetMovieStatus(Expression> where, int userId); + + /// + /// Set the watch status of a movie + /// + /// The id of the movie. + /// The id of the user. + /// The new status. + /// Where the user has stopped watching. Only usable if Status + /// is + /// The movie's status + Task SetMovieStatus(int movieId, int userId, WatchStatus status, int? watchedTime); +} diff --git a/back/src/Kyoo.Core/Controllers/LibraryManager.cs b/back/src/Kyoo.Core/Controllers/LibraryManager.cs index f9a1565f..ad94977f 100644 --- a/back/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/back/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -31,6 +31,8 @@ namespace Kyoo.Core.Controllers public LibraryManager( IRepository libraryItemRepository, + IRepository newsRepository, + IWatchItemsRepository watchItemsRepository, IRepository collectionRepository, IRepository movieRepository, IRepository showRepository, @@ -41,6 +43,8 @@ namespace Kyoo.Core.Controllers IRepository userRepository) { LibraryItems = libraryItemRepository; + News = newsRepository; + WatchItems = watchItemsRepository; Collections = collectionRepository; Movies = movieRepository; Shows = showRepository; @@ -53,6 +57,8 @@ namespace Kyoo.Core.Controllers _repositories = new IBaseRepository[] { LibraryItems, + News, + WatchItems, Collections, Movies, Shows, @@ -67,6 +73,12 @@ namespace Kyoo.Core.Controllers /// public IRepository LibraryItems { get; } + /// + public IRepository News { get; } + + /// + public IWatchItemsRepository WatchItems { get; } + /// public IRepository Collections { get; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/WatchItemsRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/WatchItemsRepository.cs new file mode 100644 index 00000000..c2974767 --- /dev/null +++ b/back/src/Kyoo.Core/Controllers/Repositories/WatchItemsRepository.cs @@ -0,0 +1,68 @@ +// Kyoo - A portable and vast media library solution. +// Copyright (c) Kyoo. +// +// See AUTHORS.md and LICENSE file in the project root for full license information. +// +// Kyoo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// Kyoo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kyoo. If not, see . + +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Kyoo.Abstractions.Controllers; +using Kyoo.Abstractions.Models; +using Kyoo.Postgresql; +using Microsoft.EntityFrameworkCore; + +namespace Kyoo.Core.Controllers; + +public class WatchItemsRepository : IWatchItemsRepository +{ + private readonly DatabaseContext _database; + + public WatchItemsRepository(DatabaseContext database) + { + _database = database; + } + + /// + public Task GetMovieStatus(Expression> where, int userId) + { + return _database.MovieWatchInfo.FirstOrDefaultAsync(x => + x.Movie == _database.Movies.FirstOrDefault(where) + && x.UserId == userId + ); + } + + /// + public async Task SetMovieStatus( + int movieId, + int userId, + WatchStatus status, + int? watchedTime) + { + if (watchedTime.HasValue && status != WatchStatus.Watching) + throw new ValidationException("Can't have a watched time if the status is not watching."); + MovieWatchStatus ret = new() + { + UserId = userId, + MovieId = movieId, + Status = status, + WatchedTime = watchedTime, + }; + await _database.MovieWatchInfo.Upsert(ret).RunAsync(); + return ret; + } +} diff --git a/back/src/Kyoo.Core/Kyoo.Core.csproj b/back/src/Kyoo.Core/Kyoo.Core.csproj index ff3ed4e0..59952646 100644 --- a/back/src/Kyoo.Core/Kyoo.Core.csproj +++ b/back/src/Kyoo.Core/Kyoo.Core.csproj @@ -10,6 +10,7 @@ + diff --git a/back/src/Kyoo.Core/Views/Resources/MovieApi.cs b/back/src/Kyoo.Core/Views/Resources/MovieApi.cs index 6cfec030..5aec36b3 100644 --- a/back/src/Kyoo.Core/Views/Resources/MovieApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/MovieApi.cs @@ -24,6 +24,7 @@ using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; +using Kyoo.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using static Kyoo.Abstractions.Models.Utils.Constants; @@ -118,7 +119,7 @@ namespace Kyoo.Core.Api /// /// List the collections that contain this show. /// - /// The ID or slug of the . + /// The ID or slug of the . /// A key to sort collections by. /// An optional list of filters. /// The number of collections to return. @@ -149,5 +150,64 @@ namespace Kyoo.Core.Api return NotFound(); return Page(resources, pagination.Limit); } + + /// + /// Get watch status + /// + /// + /// Get when an item has been wathed and if it was watched. + /// + /// The ID or slug of the . + /// The status. + /// This movie does not have a specific status. + /// No movie with the given ID or slug could be found. + [HttpGet("{identifier:id}/watchStatus")] + [HttpGet("{identifier:id}/watchStatus", Order = AlternativeRoute)] + [PartialPermission(Kind.Read)] + [UserOnly] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetWatchStatus(Identifier identifier) + { + return await _libraryManager.WatchItems.GetMovieStatus( + identifier.IsSame(), + User.GetId()!.Value + ); + } + + /// + /// Set watch status + /// + /// + /// Set when an item has been wathed and if it was watched. + /// + /// The ID or slug of the . + /// The new watch status. + /// Where the user stopped watching. + /// The newly set status. + /// The status has been set + /// WatchedTime can't be specified if status is not watching. + /// No movie with the given ID or slug could be found. + [HttpGet("{identifier:id}/watchStatus")] + [HttpGet("{identifier:id}/watchStatus", Order = AlternativeRoute)] + [PartialPermission(Kind.Read)] + [UserOnly] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task SetWatchStatus(Identifier identifier, WatchStatus status, int? watchedTime) + { + int id = await identifier.Match( + id => Task.FromResult(id), + async slug => (await _libraryManager.Movies.Get(slug)).Id + ); + return await _libraryManager.WatchItems.SetMovieStatus( + id, + User.GetId()!.Value, + status, + watchedTime + ); + } } }