diff --git a/API.Tests/Extensions/SeriesFilterTests.cs b/API.Tests/Extensions/SeriesFilterTests.cs index 3f4715f18..8f64133bf 100644 --- a/API.Tests/Extensions/SeriesFilterTests.cs +++ b/API.Tests/Extensions/SeriesFilterTests.cs @@ -935,7 +935,7 @@ public class SeriesFilterTests : AbstractDbTest var zeroRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2); Assert.NotNull(zeroRating); - Assert.True(await ratingService.UpdateRating(user, new UpdateRatingDto() + Assert.True(await ratingService.UpdateSeriesRating(user, new UpdateRatingDto() { SeriesId = zeroRating.Id, UserRating = 0 @@ -944,7 +944,7 @@ public class SeriesFilterTests : AbstractDbTest // Select 4.5 Rating var partialRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(3); - Assert.True(await ratingService.UpdateRating(user, new UpdateRatingDto() + Assert.True(await ratingService.UpdateSeriesRating(user, new UpdateRatingDto() { SeriesId = partialRating.Id, UserRating = 4.5f diff --git a/API.Tests/Services/RatingServiceTests.cs b/API.Tests/Services/RatingServiceTests.cs index f03ec7a27..5cb17f8b5 100644 --- a/API.Tests/Services/RatingServiceTests.cs +++ b/API.Tests/Services/RatingServiceTests.cs @@ -45,7 +45,7 @@ public class RatingServiceTests: AbstractDbTest var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); JobStorage.Current = new InMemoryStorage(); - var result = await _ratingService.UpdateRating(user, new UpdateRatingDto + var result = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto { SeriesId = 1, UserRating = 3, @@ -79,7 +79,7 @@ public class RatingServiceTests: AbstractDbTest var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - var result = await _ratingService.UpdateRating(user, new UpdateRatingDto + var result = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto { SeriesId = 1, UserRating = 3, @@ -95,7 +95,7 @@ public class RatingServiceTests: AbstractDbTest // Update the DB again - var result2 = await _ratingService.UpdateRating(user, new UpdateRatingDto + var result2 = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto { SeriesId = 1, UserRating = 5, @@ -129,7 +129,7 @@ public class RatingServiceTests: AbstractDbTest var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - var result = await _ratingService.UpdateRating(user, new UpdateRatingDto + var result = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto { SeriesId = 1, UserRating = 10, @@ -164,7 +164,7 @@ public class RatingServiceTests: AbstractDbTest var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - var result = await _ratingService.UpdateRating(user, new UpdateRatingDto + var result = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto { SeriesId = 2, UserRating = 5, diff --git a/API/Controllers/ChapterController.cs b/API/Controllers/ChapterController.cs index 8100578e2..835102914 100644 --- a/API/Controllers/ChapterController.cs +++ b/API/Controllers/ChapterController.cs @@ -399,7 +399,7 @@ public class ChapterController : BaseApiController [HttpGet("chapter-detail-plus")] - public async Task ChapterDetailPlus([FromQuery] int chapterId) + public async Task> ChapterDetailPlus([FromQuery] int chapterId) { var ret = new ChapterDetailPlusDto(); @@ -415,11 +415,10 @@ public class ChapterController : BaseApiController ret.HasBeenRated = ownRating.HasBeenRated; } - var externalMetadata = await _unitOfWork.ExternalChapterMetadataRepository.Get(chapterId); - if (externalMetadata != null && externalMetadata.ExternalReviews.Count > 0) + var externalReviews = await _unitOfWork.ChapterRepository.GetExternalChapterReviews(chapterId); + if (externalReviews.Count > 0) { - var dtos = externalMetadata.ExternalReviews.Select(ex => _mapper.Map(ex)).ToList(); - userReviews.AddRange(ReviewHelper.SelectSpectrumOfReviews(dtos)); + userReviews.AddRange(ReviewHelper.SelectSpectrumOfReviews(externalReviews)); } ret.Reviews = userReviews; diff --git a/API/Controllers/RatingController.cs b/API/Controllers/RatingController.cs index 7290bd1bc..c577239ed 100644 --- a/API/Controllers/RatingController.cs +++ b/API/Controllers/RatingController.cs @@ -1,17 +1,12 @@ using System; -using System.Collections.Generic; -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; using API.Services.Plus; -using EasyCaching.Core; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace API.Controllers; @@ -33,13 +28,19 @@ public class RatingController : BaseApiController _localizationService = localizationService; } - [HttpPost] - public async Task UpdateRating(UpdateRatingDto updateRating) + /// + /// Update the users' rating of the given series + /// + /// + /// + /// + [HttpPost("series")] + public async Task UpdateSeriesRating(UpdateRatingDto updateRating) { var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings | AppUserIncludes.ChapterRatings); if (user == null) throw new UnauthorizedAccessException(); - if (await _ratingService.UpdateRating(user, updateRating)) + if (await _ratingService.UpdateSeriesRating(user, updateRating)) { return Ok(); } @@ -47,24 +48,44 @@ public class RatingController : BaseApiController return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error")); } - [HttpGet("overall")] - public async Task> GetOverallRating(int seriesId, [FromQuery] int? chapterId) + /// + /// Update the users' rating of the given chapter + /// + /// chapterId must be set + /// + /// + [HttpPost("chapter")] + public async Task UpdateChapterRating(UpdateRatingDto updateRating) { - int average; - if (chapterId != null) + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings | AppUserIncludes.ChapterRatings); + if (user == null) throw new UnauthorizedAccessException(); + + if (await _ratingService.UpdateChapterRating(user, updateRating)) { - average = await _unitOfWork.ChapterRepository.GetAverageUserRating(chapterId.Value, User.GetUserId()); - } - else - { - average = await _unitOfWork.SeriesRepository.GetAverageUserRating(seriesId, User.GetUserId()); + return Ok(); } + return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error")); + } + [HttpGet("overall-series")] + public async Task> GetOverallSeriesRating(int seriesId) + { return Ok(new RatingDto() { Provider = ScrobbleProvider.Kavita, - AverageScore = average, + AverageScore = await _unitOfWork.SeriesRepository.GetAverageUserRating(seriesId, User.GetUserId()), + FavoriteCount = 0, + }); + } + + [HttpGet("overall-chapter")] + public async Task> GetOverallChapterRating(int chapterId) + { + return Ok(new RatingDto() + { + Provider = ScrobbleProvider.Kavita, + AverageScore = await _unitOfWork.ChapterRepository.GetAverageUserRating(chapterId, User.GetUserId()), FavoriteCount = 0, }); } diff --git a/API/Controllers/ReviewController.cs b/API/Controllers/ReviewController.cs index 69828d6fd..d4de3db16 100644 --- a/API/Controllers/ReviewController.cs +++ b/API/Controllers/ReviewController.cs @@ -33,39 +33,16 @@ public class ReviewController : BaseApiController /// - /// Updates the review for a given series, or chapter + /// Updates the user's review for a given series /// /// /// - [HttpPost] - public async Task> UpdateReview(UpdateUserReviewDto dto) + [HttpPost("series")] + public async Task> UpdateSeriesReview(UpdateUserReviewDto dto) { - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings | AppUserIncludes.ChapterRatings); + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings); if (user == null) return Unauthorized(); - UserReviewDto review; - if (dto.ChapterId != null) - { - review = await UpdateChapterReview(user, dto, dto.ChapterId.Value); - } - else - { - review = await UpdateSeriesReview(user, dto); - } - _unitOfWork.UserRepository.Update(user); - - await _unitOfWork.CommitAsync(); - - if (dto.ChapterId == null) - { - BackgroundJob.Enqueue(() => - _scrobblingService.ScrobbleReviewUpdate(user.Id, dto.SeriesId, string.Empty, dto.Body)); - } - return Ok(review); - } - - private async Task UpdateSeriesReview(AppUser user, UpdateUserReviewDto dto) - { var ratingBuilder = new RatingBuilder(await _unitOfWork.UserRepository.GetUserRatingAsync(dto.SeriesId, user.Id)); var rating = ratingBuilder @@ -78,11 +55,31 @@ public class ReviewController : BaseApiController { user.Ratings.Add(rating); } - return _mapper.Map(rating); + + _unitOfWork.UserRepository.Update(user); + + await _unitOfWork.CommitAsync(); + + BackgroundJob.Enqueue(() => + _scrobblingService.ScrobbleReviewUpdate(user.Id, dto.SeriesId, string.Empty, dto.Body)); + return Ok(_mapper.Map(rating)); } - private async Task UpdateChapterReview(AppUser user, UpdateUserReviewDto dto, int chapterId) + /// + /// Update the user's review for a given chapter + /// + /// chapterId must be set + /// + [HttpPost("chapter")] + public async Task> UpdateChapterReview(UpdateUserReviewDto dto) { + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.ChapterRatings); + if (user == null) return Unauthorized(); + + if (dto.ChapterId == null) return BadRequest(); + + int chapterId = dto.ChapterId.Value; + var ratingBuilder = new ChapterRatingBuilder(await _unitOfWork.UserRepository.GetUserChapterRatingAsync(user.Id, chapterId)); var rating = ratingBuilder @@ -95,28 +92,45 @@ public class ReviewController : BaseApiController { user.ChapterRatings.Add(rating); } - return _mapper.Map(rating); + + _unitOfWork.UserRepository.Update(user); + + await _unitOfWork.CommitAsync(); + + return Ok(_mapper.Map(rating)); } /// - /// Deletes the user's review for the given series, or chapter + /// Deletes the user's review for the given series /// /// - [HttpDelete] - public async Task DeleteReview([FromQuery] int seriesId, [FromQuery] int? chapterId) + [HttpDelete("series")] + public async Task DeleteSeriesReview([FromQuery] int seriesId) { var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings); if (user == null) return Unauthorized(); - if (chapterId != null) - { - user.ChapterRatings = user.ChapterRatings.Where(r => r.ChapterId != chapterId).ToList(); - } - else - { - user.Ratings = user.Ratings.Where(r => r.SeriesId != seriesId).ToList(); - } + user.Ratings = user.Ratings.Where(r => r.SeriesId != seriesId).ToList(); + + _unitOfWork.UserRepository.Update(user); + + await _unitOfWork.CommitAsync(); + + return Ok(); + } + + /// + /// Deletes the user's review for the given chapter + /// + /// + [HttpDelete("chapter")] + public async Task DeleteChapterReview([FromQuery] int chapterId) + { + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.ChapterRatings); + if (user == null) return Unauthorized(); + + user.ChapterRatings = user.ChapterRatings.Where(r => r.ChapterId != chapterId).ToList(); _unitOfWork.UserRepository.Update(user); diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs index 5849d3fd6..c83ff2fa1 100644 --- a/API/Data/DataContext.cs +++ b/API/Data/DataContext.cs @@ -79,8 +79,6 @@ public sealed class DataContext : IdentityDbContext MetadataSettings { get; set; } = null!; public DbSet MetadataFieldMapping { get; set; } = null!; public DbSet AppUserChapterRating { get; set; } = null!; - public DbSet ExternalChapterReview { get; set; } = null!; - public DbSet ExternalChapterMetadata { get; set; } = null!; protected override void OnModelCreating(ModelBuilder builder) { diff --git a/API/Data/Migrations/20250428151027_ChapterRating.cs b/API/Data/Migrations/20250428151027_ChapterRating.cs deleted file mode 100644 index 8aaec6d1f..000000000 --- a/API/Data/Migrations/20250428151027_ChapterRating.cs +++ /dev/null @@ -1,159 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace API.Data.Migrations -{ - /// - public partial class ChapterRating : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AppUserChapterRating", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Rating = table.Column(type: "REAL", nullable: false), - HasBeenRated = table.Column(type: "INTEGER", nullable: false), - Review = table.Column(type: "TEXT", nullable: true), - SeriesId = table.Column(type: "INTEGER", nullable: false), - ChapterId = table.Column(type: "INTEGER", nullable: false), - AppUserId = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AppUserChapterRating", x => x.Id); - table.ForeignKey( - name: "FK_AppUserChapterRating_AspNetUsers_AppUserId", - column: x => x.AppUserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AppUserChapterRating_Chapter_ChapterId", - column: x => x.ChapterId, - principalTable: "Chapter", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AppUserChapterRating_Series_SeriesId", - column: x => x.SeriesId, - principalTable: "Series", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ExternalChapterMetadata", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ChapterId = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ExternalChapterMetadata", x => x.Id); - table.ForeignKey( - name: "FK_ExternalChapterMetadata_Chapter_ChapterId", - column: x => x.ChapterId, - principalTable: "Chapter", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ExternalChapterReview", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Tagline = table.Column(type: "TEXT", nullable: true), - Body = table.Column(type: "TEXT", nullable: true), - BodyJustText = table.Column(type: "TEXT", nullable: true), - RawBody = table.Column(type: "TEXT", nullable: true), - Provider = table.Column(type: "INTEGER", nullable: false), - Authority = table.Column(type: "INTEGER", nullable: false), - SiteUrl = table.Column(type: "TEXT", nullable: true), - Username = table.Column(type: "TEXT", nullable: true), - Rating = table.Column(type: "INTEGER", nullable: false), - Score = table.Column(type: "INTEGER", nullable: false), - TotalVotes = table.Column(type: "INTEGER", nullable: false), - ChapterId = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ExternalChapterReview", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "ExternalChapterMetadataExternalChapterReview", - columns: table => new - { - ExternalChapterMetadatasId = table.Column(type: "INTEGER", nullable: false), - ExternalReviewsId = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ExternalChapterMetadataExternalChapterReview", x => new { x.ExternalChapterMetadatasId, x.ExternalReviewsId }); - table.ForeignKey( - name: "FK_ExternalChapterMetadataExternalChapterReview_ExternalChapterMetadata_ExternalChapterMetadatasId", - column: x => x.ExternalChapterMetadatasId, - principalTable: "ExternalChapterMetadata", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_ExternalChapterMetadataExternalChapterReview_ExternalChapterReview_ExternalReviewsId", - column: x => x.ExternalReviewsId, - principalTable: "ExternalChapterReview", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AppUserChapterRating_AppUserId", - table: "AppUserChapterRating", - column: "AppUserId"); - - migrationBuilder.CreateIndex( - name: "IX_AppUserChapterRating_ChapterId", - table: "AppUserChapterRating", - column: "ChapterId"); - - migrationBuilder.CreateIndex( - name: "IX_AppUserChapterRating_SeriesId", - table: "AppUserChapterRating", - column: "SeriesId"); - - migrationBuilder.CreateIndex( - name: "IX_ExternalChapterMetadata_ChapterId", - table: "ExternalChapterMetadata", - column: "ChapterId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_ExternalChapterMetadataExternalChapterReview_ExternalReviewsId", - table: "ExternalChapterMetadataExternalChapterReview", - column: "ExternalReviewsId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AppUserChapterRating"); - - migrationBuilder.DropTable( - name: "ExternalChapterMetadataExternalChapterReview"); - - migrationBuilder.DropTable( - name: "ExternalChapterMetadata"); - - migrationBuilder.DropTable( - name: "ExternalChapterReview"); - } - } -} diff --git a/API/Data/Migrations/20250428151027_ChapterRating.Designer.cs b/API/Data/Migrations/20250428180534_ChapterRating.Designer.cs similarity index 97% rename from API/Data/Migrations/20250428151027_ChapterRating.Designer.cs rename to API/Data/Migrations/20250428180534_ChapterRating.Designer.cs index c6812ba7e..8eb9c1fda 100644 --- a/API/Data/Migrations/20250428151027_ChapterRating.Designer.cs +++ b/API/Data/Migrations/20250428180534_ChapterRating.Designer.cs @@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace API.Data.Migrations { [DbContext(typeof(DataContext))] - [Migration("20250428151027_ChapterRating")] + [Migration("20250428180534_ChapterRating")] partial class ChapterRating { /// @@ -1348,70 +1348,6 @@ namespace API.Data.Migrations b.ToTable("MediaError"); }); - modelBuilder.Entity("API.Entities.Metadata.ExternalChapterMetadata", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ChapterId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ChapterId") - .IsUnique(); - - b.ToTable("ExternalChapterMetadata"); - }); - - modelBuilder.Entity("API.Entities.Metadata.ExternalChapterReview", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Authority") - .HasColumnType("INTEGER"); - - b.Property("Body") - .HasColumnType("TEXT"); - - b.Property("BodyJustText") - .HasColumnType("TEXT"); - - b.Property("ChapterId") - .HasColumnType("INTEGER"); - - b.Property("Provider") - .HasColumnType("INTEGER"); - - b.Property("Rating") - .HasColumnType("INTEGER"); - - b.Property("RawBody") - .HasColumnType("TEXT"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("SiteUrl") - .HasColumnType("TEXT"); - - b.Property("Tagline") - .HasColumnType("TEXT"); - - b.Property("TotalVotes") - .HasColumnType("INTEGER"); - - b.Property("Username") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ExternalChapterReview"); - }); - modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b => { b.Property("Id") @@ -1481,12 +1417,18 @@ namespace API.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Authority") + .HasColumnType("INTEGER"); + b.Property("Body") .HasColumnType("TEXT"); b.Property("BodyJustText") .HasColumnType("TEXT"); + b.Property("ChapterId") + .HasColumnType("INTEGER"); + b.Property("Provider") .HasColumnType("INTEGER"); @@ -1516,6 +1458,8 @@ namespace API.Data.Migrations b.HasKey("Id"); + b.HasIndex("ChapterId"); + b.ToTable("ExternalReview"); }); @@ -2550,21 +2494,6 @@ namespace API.Data.Migrations b.ToTable("CollectionTagSeriesMetadata"); }); - modelBuilder.Entity("ExternalChapterMetadataExternalChapterReview", b => - { - b.Property("ExternalChapterMetadatasId") - .HasColumnType("INTEGER"); - - b.Property("ExternalReviewsId") - .HasColumnType("INTEGER"); - - b.HasKey("ExternalChapterMetadatasId", "ExternalReviewsId"); - - b.HasIndex("ExternalReviewsId"); - - b.ToTable("ExternalChapterMetadataExternalChapterReview"); - }); - modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => { b.Property("ExternalRatingsId") @@ -3049,13 +2978,11 @@ namespace API.Data.Migrations b.Navigation("Chapter"); }); - modelBuilder.Entity("API.Entities.Metadata.ExternalChapterMetadata", b => + modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b => { b.HasOne("API.Entities.Chapter", null) - .WithOne("ExternalChapterMetadata") - .HasForeignKey("API.Entities.Metadata.ExternalChapterMetadata", "ChapterId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .WithMany("ExternalReviews") + .HasForeignKey("ChapterId"); }); modelBuilder.Entity("API.Entities.Metadata.ExternalSeriesMetadata", b => @@ -3365,21 +3292,6 @@ namespace API.Data.Migrations .IsRequired(); }); - modelBuilder.Entity("ExternalChapterMetadataExternalChapterReview", b => - { - b.HasOne("API.Entities.Metadata.ExternalChapterMetadata", null) - .WithMany() - .HasForeignKey("ExternalChapterMetadatasId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("API.Entities.Metadata.ExternalChapterReview", null) - .WithMany() - .HasForeignKey("ExternalReviewsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => { b.HasOne("API.Entities.Metadata.ExternalRating", null) @@ -3533,7 +3445,7 @@ namespace API.Data.Migrations modelBuilder.Entity("API.Entities.Chapter", b => { - b.Navigation("ExternalChapterMetadata"); + b.Navigation("ExternalReviews"); b.Navigation("Files"); diff --git a/API/Data/Migrations/20250428180534_ChapterRating.cs b/API/Data/Migrations/20250428180534_ChapterRating.cs new file mode 100644 index 000000000..a75d5c24b --- /dev/null +++ b/API/Data/Migrations/20250428180534_ChapterRating.cs @@ -0,0 +1,113 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Data.Migrations +{ + /// + public partial class ChapterRating : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Authority", + table: "ExternalReview", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "ChapterId", + table: "ExternalReview", + type: "INTEGER", + nullable: true); + + migrationBuilder.CreateTable( + name: "AppUserChapterRating", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Rating = table.Column(type: "REAL", nullable: false), + HasBeenRated = table.Column(type: "INTEGER", nullable: false), + Review = table.Column(type: "TEXT", nullable: true), + SeriesId = table.Column(type: "INTEGER", nullable: false), + ChapterId = table.Column(type: "INTEGER", nullable: false), + AppUserId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppUserChapterRating", x => x.Id); + table.ForeignKey( + name: "FK_AppUserChapterRating_AspNetUsers_AppUserId", + column: x => x.AppUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AppUserChapterRating_Chapter_ChapterId", + column: x => x.ChapterId, + principalTable: "Chapter", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AppUserChapterRating_Series_SeriesId", + column: x => x.SeriesId, + principalTable: "Series", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ExternalReview_ChapterId", + table: "ExternalReview", + column: "ChapterId"); + + migrationBuilder.CreateIndex( + name: "IX_AppUserChapterRating_AppUserId", + table: "AppUserChapterRating", + column: "AppUserId"); + + migrationBuilder.CreateIndex( + name: "IX_AppUserChapterRating_ChapterId", + table: "AppUserChapterRating", + column: "ChapterId"); + + migrationBuilder.CreateIndex( + name: "IX_AppUserChapterRating_SeriesId", + table: "AppUserChapterRating", + column: "SeriesId"); + + migrationBuilder.AddForeignKey( + name: "FK_ExternalReview_Chapter_ChapterId", + table: "ExternalReview", + column: "ChapterId", + principalTable: "Chapter", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ExternalReview_Chapter_ChapterId", + table: "ExternalReview"); + + migrationBuilder.DropTable( + name: "AppUserChapterRating"); + + migrationBuilder.DropIndex( + name: "IX_ExternalReview_ChapterId", + table: "ExternalReview"); + + migrationBuilder.DropColumn( + name: "Authority", + table: "ExternalReview"); + + migrationBuilder.DropColumn( + name: "ChapterId", + table: "ExternalReview"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index e32084836..46dabef6a 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -1345,70 +1345,6 @@ namespace API.Data.Migrations b.ToTable("MediaError"); }); - modelBuilder.Entity("API.Entities.Metadata.ExternalChapterMetadata", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ChapterId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ChapterId") - .IsUnique(); - - b.ToTable("ExternalChapterMetadata"); - }); - - modelBuilder.Entity("API.Entities.Metadata.ExternalChapterReview", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Authority") - .HasColumnType("INTEGER"); - - b.Property("Body") - .HasColumnType("TEXT"); - - b.Property("BodyJustText") - .HasColumnType("TEXT"); - - b.Property("ChapterId") - .HasColumnType("INTEGER"); - - b.Property("Provider") - .HasColumnType("INTEGER"); - - b.Property("Rating") - .HasColumnType("INTEGER"); - - b.Property("RawBody") - .HasColumnType("TEXT"); - - b.Property("Score") - .HasColumnType("INTEGER"); - - b.Property("SiteUrl") - .HasColumnType("TEXT"); - - b.Property("Tagline") - .HasColumnType("TEXT"); - - b.Property("TotalVotes") - .HasColumnType("INTEGER"); - - b.Property("Username") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ExternalChapterReview"); - }); - modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b => { b.Property("Id") @@ -1478,12 +1414,18 @@ namespace API.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Authority") + .HasColumnType("INTEGER"); + b.Property("Body") .HasColumnType("TEXT"); b.Property("BodyJustText") .HasColumnType("TEXT"); + b.Property("ChapterId") + .HasColumnType("INTEGER"); + b.Property("Provider") .HasColumnType("INTEGER"); @@ -1513,6 +1455,8 @@ namespace API.Data.Migrations b.HasKey("Id"); + b.HasIndex("ChapterId"); + b.ToTable("ExternalReview"); }); @@ -2547,21 +2491,6 @@ namespace API.Data.Migrations b.ToTable("CollectionTagSeriesMetadata"); }); - modelBuilder.Entity("ExternalChapterMetadataExternalChapterReview", b => - { - b.Property("ExternalChapterMetadatasId") - .HasColumnType("INTEGER"); - - b.Property("ExternalReviewsId") - .HasColumnType("INTEGER"); - - b.HasKey("ExternalChapterMetadatasId", "ExternalReviewsId"); - - b.HasIndex("ExternalReviewsId"); - - b.ToTable("ExternalChapterMetadataExternalChapterReview"); - }); - modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => { b.Property("ExternalRatingsId") @@ -3046,13 +2975,11 @@ namespace API.Data.Migrations b.Navigation("Chapter"); }); - modelBuilder.Entity("API.Entities.Metadata.ExternalChapterMetadata", b => + modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b => { b.HasOne("API.Entities.Chapter", null) - .WithOne("ExternalChapterMetadata") - .HasForeignKey("API.Entities.Metadata.ExternalChapterMetadata", "ChapterId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .WithMany("ExternalReviews") + .HasForeignKey("ChapterId"); }); modelBuilder.Entity("API.Entities.Metadata.ExternalSeriesMetadata", b => @@ -3362,21 +3289,6 @@ namespace API.Data.Migrations .IsRequired(); }); - modelBuilder.Entity("ExternalChapterMetadataExternalChapterReview", b => - { - b.HasOne("API.Entities.Metadata.ExternalChapterMetadata", null) - .WithMany() - .HasForeignKey("ExternalChapterMetadatasId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("API.Entities.Metadata.ExternalChapterReview", null) - .WithMany() - .HasForeignKey("ExternalReviewsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => { b.HasOne("API.Entities.Metadata.ExternalRating", null) @@ -3530,7 +3442,7 @@ namespace API.Data.Migrations modelBuilder.Entity("API.Entities.Chapter", b => { - b.Navigation("ExternalChapterMetadata"); + b.Navigation("ExternalReviews"); b.Navigation("Files"); diff --git a/API/Data/Repositories/ChapterRepository.cs b/API/Data/Repositories/ChapterRepository.cs index 9e07c39b1..5a73f3bed 100644 --- a/API/Data/Repositories/ChapterRepository.cs +++ b/API/Data/Repositories/ChapterRepository.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using API.DTOs; using API.DTOs.Metadata; using API.DTOs.Reader; +using API.DTOs.SeriesDetail; using API.Entities; using API.Entities.Enums; using API.Extensions; @@ -24,7 +25,8 @@ public enum ChapterIncludes Files = 4, People = 8, Genres = 16, - Tags = 32 + Tags = 32, + ExternalReviews = 1 << 6, } public interface IChapterRepository @@ -49,6 +51,7 @@ public interface IChapterRepository IEnumerable GetChaptersForSeries(int seriesId); Task> GetAllChaptersForSeries(int seriesId); Task GetAverageUserRating(int chapterId, int userId); + Task> GetExternalChapterReviews(int chapterId); } public class ChapterRepository : IChapterRepository { @@ -327,4 +330,14 @@ public class ChapterRepository : IChapterRepository .AverageAsync(r => (int?) r.Rating)); return avg.HasValue ? (int) (avg.Value * 20) : 0; } + + public async Task> GetExternalChapterReviews(int chapterId) + { + return await _context.Chapter + .Where(c => c.Id == chapterId) + .SelectMany(c => c.ExternalReviews) + // Don't use ProjectTo, it fails to map int to float (??) + .Select(r => _mapper.Map(r)) + .ToListAsync(); + } } diff --git a/API/Data/Repositories/ExternalChapterMetadataRepository.cs b/API/Data/Repositories/ExternalChapterMetadataRepository.cs deleted file mode 100644 index bd35a5c3a..000000000 --- a/API/Data/Repositories/ExternalChapterMetadataRepository.cs +++ /dev/null @@ -1,44 +0,0 @@ -#nullable enable -using System.Collections.Generic; -using System.Threading.Tasks; -using API.Entities.Metadata; -using API.Extensions.QueryExtensions; -using AutoMapper; -using Microsoft.EntityFrameworkCore; - -namespace API.Data.Repositories; - -public enum ExternalChapterMetadataIncludes -{ - None = 0, - ExternalReviews = 1 << 1, -} - -public interface IExternalChapterMetadataRepository -{ - void Attach(ExternalChapterMetadata externalChapterMetadata); - void Remove(IEnumerable? reviews); - - Task Get(int chapterId, ExternalChapterMetadataIncludes includes = ExternalChapterMetadataIncludes.ExternalReviews); -} - -public class ExternalChapterMetadataRepository(DataContext context, IMapper mapper): IExternalChapterMetadataRepository -{ - - public void Attach(ExternalChapterMetadata externalChapterMetadata) - { - context.ExternalChapterMetadata.Attach(externalChapterMetadata); - } - public void Remove(IEnumerable? reviews) - { - if (reviews == null) return; - context.ExternalChapterReview.RemoveRange(reviews); - - } - public async Task Get(int chapterId, ExternalChapterMetadataIncludes includes = ExternalChapterMetadataIncludes.ExternalReviews) - { - return await context.ExternalChapterMetadata - .Includes(includes) - .FirstOrDefaultAsync(c => c.ChapterId == chapterId); - } -} diff --git a/API/Data/UnitOfWork.cs b/API/Data/UnitOfWork.cs index 32ddcf596..c4a07dee7 100644 --- a/API/Data/UnitOfWork.cs +++ b/API/Data/UnitOfWork.cs @@ -33,7 +33,6 @@ public interface IUnitOfWork IAppUserExternalSourceRepository AppUserExternalSourceRepository { get; } IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository { get; } IEmailHistoryRepository EmailHistoryRepository { get; } - IExternalChapterMetadataRepository ExternalChapterMetadataRepository { get; } bool Commit(); Task CommitAsync(); bool HasChanges(); @@ -75,7 +74,6 @@ public class UnitOfWork : IUnitOfWork AppUserExternalSourceRepository = new AppUserExternalSourceRepository(_context, _mapper); ExternalSeriesMetadataRepository = new ExternalSeriesMetadataRepository(_context, _mapper); EmailHistoryRepository = new EmailHistoryRepository(_context, _mapper); - ExternalChapterMetadataRepository = new ExternalChapterMetadataRepository(_context, _mapper); } /// @@ -105,7 +103,6 @@ public class UnitOfWork : IUnitOfWork public IAppUserExternalSourceRepository AppUserExternalSourceRepository { get; } public IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository { get; } public IEmailHistoryRepository EmailHistoryRepository { get; } - public IExternalChapterMetadataRepository ExternalChapterMetadataRepository { get; } /// /// Commits changes to the DB. Completes the open transaction. diff --git a/API/Entities/Chapter.cs b/API/Entities/Chapter.cs index 11585e759..5d2608557 100644 --- a/API/Entities/Chapter.cs +++ b/API/Entities/Chapter.cs @@ -170,7 +170,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage public Volume Volume { get; set; } = null!; public int VolumeId { get; set; } - public ExternalChapterMetadata ExternalChapterMetadata { get; set; } = null!; + public ICollection ExternalReviews { get; set; } = []; public void UpdateFrom(ParserInfo info) { diff --git a/API/Entities/Metadata/ExternalChapterMetadata.cs b/API/Entities/Metadata/ExternalChapterMetadata.cs deleted file mode 100644 index a4d50896f..000000000 --- a/API/Entities/Metadata/ExternalChapterMetadata.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; - -namespace API.Entities.Metadata; - -/// -/// External Metadata from Kavita+ for a Chapter -/// -/// -/// As apposed to , -/// we do not have a ValidUntilUtc, as this is only matched together with the series. -/// -public class ExternalChapterMetadata -{ - public int Id { get; set; } - - public int ChapterId { get; set; } - - public ICollection ExternalReviews { get; set; } = null!; - -} diff --git a/API/Entities/Metadata/ExternalChapterReview.cs b/API/Entities/Metadata/ExternalChapterReview.cs deleted file mode 100644 index 81222e281..000000000 --- a/API/Entities/Metadata/ExternalChapterReview.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Generic; -using API.Entities.Enums; -using API.Services.Plus; - -namespace API.Entities.Metadata; - -/// -/// Represents an Externally supplied Review for a given Series -/// -public class ExternalChapterReview -{ - public int Id { get; set; } - public string Tagline { get; set; } - public required string Body { get; set; } - /// - /// Pure text version of the body - /// - public required string BodyJustText { get; set; } - /// - /// Raw from the provider. Usually Markdown - /// - public string RawBody { get; set; } - public required ScrobbleProvider Provider { get; set; } - public RatingAuthority Authority { get; set; } = RatingAuthority.User; - public string SiteUrl { get; set; } - /// - /// Reviewer's username - /// - public string Username { get; set; } - /// - /// An Optional Rating coming from the Review - /// - public int Rating { get; set; } = 0; - /// - /// The media's overall Score - /// - public int Score { get; set; } - public int TotalVotes { get; set; } - - public int ChapterId { get; set; } - - // Relationships - public ICollection ExternalChapterMetadatas { get; set; } = null!; - -} diff --git a/API/Entities/Metadata/ExternalReview.cs b/API/Entities/Metadata/ExternalReview.cs index 6304d98ad..73c71e5ee 100644 --- a/API/Entities/Metadata/ExternalReview.cs +++ b/API/Entities/Metadata/ExternalReview.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using API.Entities.Enums; using API.Services.Plus; namespace API.Entities.Metadata; @@ -20,6 +21,7 @@ public class ExternalReview /// public string RawBody { get; set; } public required ScrobbleProvider Provider { get; set; } + public RatingAuthority Authority { get; set; } = RatingAuthority.User; public string SiteUrl { get; set; } /// /// Reviewer's username @@ -37,6 +39,7 @@ public class ExternalReview public int SeriesId { get; set; } + public int? ChapterId { get; set; } // Relationships public ICollection ExternalSeriesMetadatas { get; set; } = null!; diff --git a/API/Extensions/QueryExtensions/IncludesExtensions.cs b/API/Extensions/QueryExtensions/IncludesExtensions.cs index 92470f572..1706648c1 100644 --- a/API/Extensions/QueryExtensions/IncludesExtensions.cs +++ b/API/Extensions/QueryExtensions/IncludesExtensions.cs @@ -73,6 +73,12 @@ public static class IncludesExtensions .Include(c => c.Tags); } + if (includes.HasFlag(ChapterIncludes.ExternalReviews)) + { + queryable = queryable + .Include(c => c.ExternalReviews); + } + return queryable.AsSplitQuery(); } @@ -309,14 +315,4 @@ public static class IncludesExtensions return query.AsSplitQuery(); } - - public static IQueryable Includes(this IQueryable query, ExternalChapterMetadataIncludes includeFlags) - { - if (includeFlags.HasFlag(ExternalChapterMetadataIncludes.ExternalReviews)) - { - query = query.Include(e => e.ExternalReviews); - } - - return query.AsSplitQuery(); - } } diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs index 89c15339b..334403ab3 100644 --- a/API/Helpers/AutoMapperProfiles.cs +++ b/API/Helpers/AutoMapperProfiles.cs @@ -344,19 +344,11 @@ public class AutoMapperProfiles : Profile .ForMember(dest => dest.IsExternal, opt => opt.MapFrom(src => true)); - CreateMap() - .ForMember(dest => dest.IsExternal, - opt => - opt.MapFrom(src => true)); CreateMap() .ForMember(dest => dest.BodyJustText, opt => opt.MapFrom(src => ReviewHelper.GetCharacters(src.Body))); - CreateMap() - .ForMember(dest => dest.BodyJustText, - opt => - opt.MapFrom(src => ReviewHelper.GetCharacters(src.Body))); CreateMap(); CreateMap() diff --git a/API/Services/Plus/ExternalMetadataService.cs b/API/Services/Plus/ExternalMetadataService.cs index b003ce282..6159f91ef 100644 --- a/API/Services/Plus/ExternalMetadataService.cs +++ b/API/Services/Plus/ExternalMetadataService.cs @@ -1104,16 +1104,15 @@ public class ExternalMetadataService : IExternalMetadataService return false; } - var exteralChapterMetadata = await GetOrCreateExternalChapterMetadataForChapter(chapter.Id, chapter); - _unitOfWork.ExternalChapterMetadataRepository.Remove(exteralChapterMetadata.ExternalReviews); + _unitOfWork.ExternalSeriesMetadataRepository.Remove(chapter.ExternalReviews); - List externalReviews = []; + List externalReviews = []; externalReviews.AddRange(metadata.CriticReviews .Where(r => !string.IsNullOrWhiteSpace(r.Username) && !string.IsNullOrWhiteSpace(r.Body)) .Select(r => { - var review = _mapper.Map(r); + var review = _mapper.Map(r); review.ChapterId = chapter.Id; review.Authority = RatingAuthority.Critic; return review; @@ -1122,13 +1121,13 @@ public class ExternalMetadataService : IExternalMetadataService .Where(r => !string.IsNullOrWhiteSpace(r.Username) && !string.IsNullOrWhiteSpace(r.Body)) .Select(r => { - var review = _mapper.Map(r); + var review = _mapper.Map(r); review.ChapterId = chapter.Id; review.Authority = RatingAuthority.User; return review; })); - chapter.ExternalChapterMetadata.ExternalReviews = externalReviews; + chapter.ExternalReviews = externalReviews; _logger.LogDebug("Added {Count} reviews for chapter {ChapterId}", externalReviews.Count, chapter.Id); return true; @@ -1588,28 +1587,6 @@ public class ExternalMetadataService : IExternalMetadataService return externalSeriesMetadata; } - /// - /// Gets from DB or creates a new one with just ChapterId - /// - /// - /// - /// - private async Task GetOrCreateExternalChapterMetadataForChapter(int chapterId, Chapter chapter) - { - var externalChapterMetadata = await _unitOfWork.ExternalChapterMetadataRepository.Get(chapterId); - if (externalChapterMetadata != null) return externalChapterMetadata; - - externalChapterMetadata = new ExternalChapterMetadata() - { - ChapterId = chapterId, - }; - - chapter.ExternalChapterMetadata = externalChapterMetadata; - _unitOfWork.ExternalChapterMetadataRepository.Attach(externalChapterMetadata); - - return externalChapterMetadata; - } - private async Task ProcessRecommendations(LibraryType libraryType, IEnumerable recs, ExternalSeriesMetadata externalSeriesMetadata) { diff --git a/API/Services/RatingService.cs b/API/Services/RatingService.cs index faf86ccef..ccaebba69 100644 --- a/API/Services/RatingService.cs +++ b/API/Services/RatingService.cs @@ -14,12 +14,20 @@ namespace API.Services; public interface IRatingService { /// - /// + /// Updates the users' rating for a given series /// /// Should include ratings /// /// - Task UpdateRating(AppUser user, UpdateRatingDto updateRatingDto); + Task UpdateSeriesRating(AppUser user, UpdateRatingDto updateRatingDto); + + /// + /// Updates the users' rating for a given chapter + /// + /// Should include ratings + /// chapterId must be set + /// + Task UpdateChapterRating(AppUser user, UpdateRatingDto updateRatingDto); } public class RatingService: IRatingService @@ -36,17 +44,7 @@ public class RatingService: IRatingService _logger = logger; } - public async Task UpdateRating(AppUser user, UpdateRatingDto updateRatingDto) - { - if (updateRatingDto.ChapterId != null) - { - return await UpdateChapterRating(user, updateRatingDto); - } - - return await UpdateSeriesRating(user, updateRatingDto); - } - - private async Task UpdateSeriesRating(AppUser user, UpdateRatingDto updateRatingDto) + public async Task UpdateSeriesRating(AppUser user, UpdateRatingDto updateRatingDto) { var userRating = await _unitOfWork.UserRepository.GetUserRatingAsync(updateRatingDto.SeriesId, user.Id) ?? @@ -85,7 +83,7 @@ public class RatingService: IRatingService return false; } - private async Task UpdateChapterRating(AppUser user, UpdateRatingDto updateRatingDto) + public async Task UpdateChapterRating(AppUser user, UpdateRatingDto updateRatingDto) { if (updateRatingDto.ChapterId == null) { diff --git a/UI/Web/src/app/_services/review.service.ts b/UI/Web/src/app/_services/review.service.ts index c13522fb6..b8635bcf8 100644 --- a/UI/Web/src/app/_services/review.service.ts +++ b/UI/Web/src/app/_services/review.service.ts @@ -15,35 +15,42 @@ export class ReviewService { deleteReview(seriesId: number, chapterId?: number) { if (chapterId) { - return this.httpClient.delete(this.baseUrl + `review?chapterId=${chapterId}&seriesId=${seriesId}`); + return this.httpClient.delete(this.baseUrl + `review/chapter?chapterId=${chapterId}`); } - return this.httpClient.delete(this.baseUrl + 'review?seriesId=' + seriesId); + return this.httpClient.delete(this.baseUrl + `review/series?seriesId=${seriesId}`); } updateReview(seriesId: number, body: string, chapterId?: number) { if (chapterId) { - return this.httpClient.post(this.baseUrl + `review`, { + return this.httpClient.post(this.baseUrl + `review/chapter`, { seriesId, chapterId, body }); } - return this.httpClient.post(this.baseUrl + 'review', { + return this.httpClient.post(this.baseUrl + 'review/series', { seriesId, body }); } updateRating(seriesId: number, userRating: number, chapterId?: number) { - return this.httpClient.post(this.baseUrl + 'rating', { - seriesId, chapterId, userRating + if (chapterId) { + return this.httpClient.post(this.baseUrl + 'rating/chapter', { + seriesId, chapterId, userRating + }) + } + + return this.httpClient.post(this.baseUrl + 'rating/series', { + seriesId, userRating }) } overallRating(seriesId: number, chapterId?: number) { if (chapterId) { - return this.httpClient.get(this.baseUrl + `rating/overall?chapterId=${chapterId}&seriesId=${seriesId}`); + return this.httpClient.get(this.baseUrl + `rating/overall-chapter?chapterId=${chapterId}`); } - return this.httpClient.get(this.baseUrl + 'rating/overall?seriesId=' + seriesId); + + return this.httpClient.get(this.baseUrl + `rating/overall-series?seriesId=${seriesId}`); } }