diff --git a/API.Tests/Extensions/SeriesFilterTests.cs b/API.Tests/Extensions/SeriesFilterTests.cs index 0b517373a..3f4715f18 100644 --- a/API.Tests/Extensions/SeriesFilterTests.cs +++ b/API.Tests/Extensions/SeriesFilterTests.cs @@ -929,17 +929,13 @@ public class SeriesFilterTests : AbstractDbTest _context.Library.Add(library); await _context.SaveChangesAsync(); - - var seriesService = new SeriesService(_unitOfWork, Substitute.For(), - Substitute.For(), Substitute.For>(), - Substitute.For(), Substitute.For(), - Substitute.For()); + var ratingService = new RatingService(_unitOfWork, Substitute.For(), Substitute.For>()); // Select 0 Rating var zeroRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2); Assert.NotNull(zeroRating); - Assert.True(await seriesService.UpdateRating(user, new UpdateRatingDto() + Assert.True(await ratingService.UpdateRating(user, new UpdateRatingDto() { SeriesId = zeroRating.Id, UserRating = 0 @@ -948,7 +944,7 @@ public class SeriesFilterTests : AbstractDbTest // Select 4.5 Rating var partialRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(3); - Assert.True(await seriesService.UpdateRating(user, new UpdateRatingDto() + Assert.True(await ratingService.UpdateRating(user, new UpdateRatingDto() { SeriesId = partialRating.Id, UserRating = 4.5f diff --git a/API.Tests/Services/RatingServiceTests.cs b/API.Tests/Services/RatingServiceTests.cs new file mode 100644 index 000000000..f03ec7a27 --- /dev/null +++ b/API.Tests/Services/RatingServiceTests.cs @@ -0,0 +1,189 @@ +using System.Linq; +using System.Threading.Tasks; +using API.Data.Repositories; +using API.DTOs; +using API.Entities.Enums; +using API.Helpers.Builders; +using API.Services; +using API.Services.Plus; +using Hangfire; +using Hangfire.InMemory; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Xunit; + +namespace API.Tests.Services; + +public class RatingServiceTests: AbstractDbTest +{ + private readonly RatingService _ratingService; + + public RatingServiceTests() + { + _ratingService = new RatingService(_unitOfWork, Substitute.For(), Substitute.For>()); + } + + [Fact] + public async Task UpdateRating_ShouldSetRating() + { + await ResetDb(); + + _context.Library.Add(new LibraryBuilder("Test LIb") + .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) + .WithSeries(new SeriesBuilder("Test") + + .WithVolume(new VolumeBuilder("1") + .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) + .Build()) + .Build()) + .Build()); + + + await _context.SaveChangesAsync(); + + + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); + + JobStorage.Current = new InMemoryStorage(); + var result = await _ratingService.UpdateRating(user, new UpdateRatingDto + { + SeriesId = 1, + UserRating = 3, + }); + + Assert.True(result); + + var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings))! + .Ratings; + Assert.NotEmpty(ratings); + Assert.Equal(3, ratings.First().Rating); + } + + [Fact] + public async Task UpdateRating_ShouldUpdateExistingRating() + { + await ResetDb(); + + _context.Library.Add(new LibraryBuilder("Test LIb") + .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) + .WithSeries(new SeriesBuilder("Test") + + .WithVolume(new VolumeBuilder("1") + .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) + .Build()) + .Build()) + .Build()); + + + await _context.SaveChangesAsync(); + + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); + + var result = await _ratingService.UpdateRating(user, new UpdateRatingDto + { + SeriesId = 1, + UserRating = 3, + }); + + Assert.True(result); + + JobStorage.Current = new InMemoryStorage(); + var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings)) + .Ratings; + Assert.NotEmpty(ratings); + Assert.Equal(3, ratings.First().Rating); + + // Update the DB again + + var result2 = await _ratingService.UpdateRating(user, new UpdateRatingDto + { + SeriesId = 1, + UserRating = 5, + }); + + Assert.True(result2); + + var ratings2 = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings)) + .Ratings; + Assert.NotEmpty(ratings2); + Assert.True(ratings2.Count == 1); + Assert.Equal(5, ratings2.First().Rating); + } + + [Fact] + public async Task UpdateRating_ShouldClampRatingAt5() + { + await ResetDb(); + + _context.Library.Add(new LibraryBuilder("Test LIb") + .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) + .WithSeries(new SeriesBuilder("Test") + + .WithVolume(new VolumeBuilder("1") + .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) + .Build()) + .Build()) + .Build()); + + await _context.SaveChangesAsync(); + + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); + + var result = await _ratingService.UpdateRating(user, new UpdateRatingDto + { + SeriesId = 1, + UserRating = 10, + }); + + Assert.True(result); + + JobStorage.Current = new InMemoryStorage(); + var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", + AppUserIncludes.Ratings)!) + .Ratings; + Assert.NotEmpty(ratings); + Assert.Equal(5, ratings.First().Rating); + } + + [Fact] + public async Task UpdateRating_ShouldReturnFalseWhenSeriesDoesntExist() + { + await ResetDb(); + + _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) + .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) + .WithSeries(new SeriesBuilder("Test") + + .WithVolume(new VolumeBuilder("1") + .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) + .Build()) + .Build()) + .Build()); + + await _context.SaveChangesAsync(); + + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); + + var result = await _ratingService.UpdateRating(user, new UpdateRatingDto + { + SeriesId = 2, + UserRating = 5, + }); + + Assert.False(result); + + var ratings = user.Ratings; + Assert.Empty(ratings); + } + protected override async Task ResetDb() + { + _context.Series.RemoveRange(_context.Series.ToList()); + _context.AppUserRating.RemoveRange(_context.AppUserRating.ToList()); + _context.Genre.RemoveRange(_context.Genre.ToList()); + _context.CollectionTag.RemoveRange(_context.CollectionTag.ToList()); + _context.Person.RemoveRange(_context.Person.ToList()); + _context.Library.RemoveRange(_context.Library.ToList()); + + await _context.SaveChangesAsync(); + } +} diff --git a/API.Tests/Services/SeriesServiceTests.cs b/API.Tests/Services/SeriesServiceTests.cs index 6856500e7..4bc6d91b4 100644 --- a/API.Tests/Services/SeriesServiceTests.cs +++ b/API.Tests/Services/SeriesServiceTests.cs @@ -590,164 +590,6 @@ public class SeriesServiceTests : AbstractDbTest - #endregion - - - #region UpdateRating - - [Fact] - public async Task UpdateRating_ShouldSetRating() - { - await ResetDb(); - - _context.Library.Add(new LibraryBuilder("Test LIb") - .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) - .WithSeries(new SeriesBuilder("Test") - - .WithVolume(new VolumeBuilder("1") - .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) - .Build()) - .Build()) - .Build()); - - - await _context.SaveChangesAsync(); - - - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - - JobStorage.Current = new InMemoryStorage(); - var result = await _seriesService.UpdateRating(user, new UpdateRatingDto - { - SeriesId = 1, - UserRating = 3, - }); - - Assert.True(result); - - var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings))! - .Ratings; - Assert.NotEmpty(ratings); - Assert.Equal(3, ratings.First().Rating); - } - - [Fact] - public async Task UpdateRating_ShouldUpdateExistingRating() - { - await ResetDb(); - - _context.Library.Add(new LibraryBuilder("Test LIb") - .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) - .WithSeries(new SeriesBuilder("Test") - - .WithVolume(new VolumeBuilder("1") - .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) - .Build()) - .Build()) - .Build()); - - - await _context.SaveChangesAsync(); - - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - - var result = await _seriesService.UpdateRating(user, new UpdateRatingDto - { - SeriesId = 1, - UserRating = 3, - }); - - Assert.True(result); - - JobStorage.Current = new InMemoryStorage(); - var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings)) - .Ratings; - Assert.NotEmpty(ratings); - Assert.Equal(3, ratings.First().Rating); - - // Update the DB again - - var result2 = await _seriesService.UpdateRating(user, new UpdateRatingDto - { - SeriesId = 1, - UserRating = 5, - }); - - Assert.True(result2); - - var ratings2 = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings)) - .Ratings; - Assert.NotEmpty(ratings2); - Assert.True(ratings2.Count == 1); - Assert.Equal(5, ratings2.First().Rating); - } - - [Fact] - public async Task UpdateRating_ShouldClampRatingAt5() - { - await ResetDb(); - - _context.Library.Add(new LibraryBuilder("Test LIb") - .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) - .WithSeries(new SeriesBuilder("Test") - - .WithVolume(new VolumeBuilder("1") - .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) - .Build()) - .Build()) - .Build()); - - await _context.SaveChangesAsync(); - - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - - var result = await _seriesService.UpdateRating(user, new UpdateRatingDto - { - SeriesId = 1, - UserRating = 10, - }); - - Assert.True(result); - - JobStorage.Current = new InMemoryStorage(); - var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", - AppUserIncludes.Ratings)!) - .Ratings; - Assert.NotEmpty(ratings); - Assert.Equal(5, ratings.First().Rating); - } - - [Fact] - public async Task UpdateRating_ShouldReturnFalseWhenSeriesDoesntExist() - { - await ResetDb(); - - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) - .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) - .WithSeries(new SeriesBuilder("Test") - - .WithVolume(new VolumeBuilder("1") - .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) - .Build()) - .Build()) - .Build()); - - await _context.SaveChangesAsync(); - - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - - var result = await _seriesService.UpdateRating(user, new UpdateRatingDto - { - SeriesId = 2, - UserRating = 5, - }); - - Assert.False(result); - - var ratings = user.Ratings; - Assert.Empty(ratings); - } - #endregion #region UpdateSeriesMetadata diff --git a/API/Controllers/RatingController.cs b/API/Controllers/RatingController.cs index 9490c41ee..0895aad1f 100644 --- a/API/Controllers/RatingController.cs +++ b/API/Controllers/RatingController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using API.Constants; using API.Data; +using API.Data.Repositories; using API.DTOs; using API.Extensions; using API.Services; @@ -35,7 +36,10 @@ public class RatingController : BaseApiController [HttpPost] public async Task UpdateRating(UpdateRatingDto updateRating) { - if (await _ratingService.UpdateRating(User.GetUserId(), updateRating)) + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings); + if (user == null) throw new UnauthorizedAccessException(); + + if (await _ratingService.UpdateRating(user, updateRating)) { return Ok(); } diff --git a/API/DTOs/SeriesDetail/UserReviewDto.cs b/API/DTOs/SeriesDetail/UserReviewDto.cs index 2ab04784e..e72c19486 100644 --- a/API/DTOs/SeriesDetail/UserReviewDto.cs +++ b/API/DTOs/SeriesDetail/UserReviewDto.cs @@ -58,5 +58,4 @@ public class UserReviewDto /// If this review is External, which Provider did it come from /// public ScrobbleProvider Provider { get; set; } = ScrobbleProvider.Kavita; - public ReviewAuthority Authority { get; set; } = ReviewAuthority.User; } diff --git a/API/Services/Plus/ExternalMetadataService.cs b/API/Services/Plus/ExternalMetadataService.cs index 424331e09..8350058b8 100644 --- a/API/Services/Plus/ExternalMetadataService.cs +++ b/API/Services/Plus/ExternalMetadataService.cs @@ -1086,6 +1086,8 @@ public class ExternalMetadataService : IExternalMetadataService madeModification = await UpdateChapterCoverImage(chapter, settings, potentialMatch.CoverImageUrl) || madeModification; + madeModification = await UpdateChapterReviews(chapter, settings, potentialMatch) || madeModification; + _unitOfWork.ChapterRepository.Update(chapter); await _unitOfWork.CommitAsync(); } @@ -1094,6 +1096,18 @@ public class ExternalMetadataService : IExternalMetadataService return madeModification; } + private async Task UpdateChapterReviews(Chapter chapter, MetadataSettingsDto settings, ExternalChapterDto metadata) + { + if (!settings.Enabled) return false; + + if (metadata.UserReviews.Count == 0 && metadata.CriticReviews.Count == 0) return false; + + // Clear current ratings + chapter.Ratings.Clear(); + + return true; + } + private static bool UpdateChapterSummary(Chapter chapter, MetadataSettingsDto settings, string? summary) { diff --git a/API/Services/RatingService.cs b/API/Services/RatingService.cs index d9504a820..be7c33a43 100644 --- a/API/Services/RatingService.cs +++ b/API/Services/RatingService.cs @@ -13,7 +13,13 @@ namespace API.Services; public interface IRatingService { - Task UpdateRating(int userId, UpdateRatingDto updateRatingDto); + /// + /// + /// + /// Should include ratings + /// + /// + Task UpdateRating(AppUser user, UpdateRatingDto updateRatingDto); } public class RatingService: IRatingService @@ -30,17 +36,8 @@ public class RatingService: IRatingService _logger = logger; } - /// - /// - /// - /// - /// - /// - public async Task UpdateRating(int userId, UpdateRatingDto updateRatingDto) + public async Task UpdateRating(AppUser user, UpdateRatingDto updateRatingDto) { - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.Ratings); - if (user == null) throw new UnauthorizedAccessException(); - var userRating = await _unitOfWork.UserRepository.GetUserRatingAsync(updateRatingDto.SeriesId, user.Id, updateRatingDto.ChapterId) ?? new AppUserRating();