Add rating to chapter page & volume (if one chapter)

This commit is contained in:
Amelia 2025-04-26 09:40:56 +02:00
parent e96cb0fde9
commit 8ccc2b5801
No known key found for this signature in database
GPG key ID: D6D0ECE365407EAA
18 changed files with 226 additions and 47 deletions

View file

@ -28,13 +28,16 @@ public class ChapterController : BaseApiController
private readonly ILocalizationService _localizationService;
private readonly IEventHub _eventHub;
private readonly ILogger<ChapterController> _logger;
private readonly IRatingService _ratingService;
public ChapterController(IUnitOfWork unitOfWork, ILocalizationService localizationService, IEventHub eventHub, ILogger<ChapterController> logger)
public ChapterController(IUnitOfWork unitOfWork, ILocalizationService localizationService, IEventHub eventHub, ILogger<ChapterController> logger,
IRatingService ratingService)
{
_unitOfWork = unitOfWork;
_localizationService = localizationService;
_eventHub = eventHub;
_logger = logger;
_ratingService = ratingService;
}
/// <summary>
@ -403,4 +406,15 @@ public class ChapterController : BaseApiController
return await _unitOfWork.UserRepository.GetUserRatingDtosForChapterAsync(chapterId, User.GetUserId());
}
[HttpPost("update-rating")]
public async Task<ActionResult> UpdateRating(UpdateChapterRatingDto dto)
{
if (await _ratingService.UpdateChapterRating(User.GetUserId(), dto))
{
return Ok();
}
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
}
}

View file

@ -38,4 +38,15 @@ public class RatingController : BaseApiController
FavoriteCount = 0
});
}
[HttpGet("overall/chapter")]
public async Task<ActionResult<RatingDto>> GetOverallChapterRating([FromQuery] int chapterId)
{
return Ok(new RatingDto
{
Provider = ScrobbleProvider.Kavita,
AverageScore = await _unitOfWork.ChapterRepository.GetAverageUserRating(chapterId, User.GetUserId()),
FavoriteCount = 0,
});
}
}

View file

@ -0,0 +1,7 @@
namespace API.DTOs;
public class UpdateChapterRatingDto
{
public int ChapterId { get; init; }
public float Rating { get; init; }
}

View file

@ -48,6 +48,7 @@ public interface IChapterRepository
Task<ChapterDto> AddChapterModifiers(int userId, ChapterDto chapter);
IEnumerable<Chapter> GetChaptersForSeries(int seriesId);
Task<IList<Chapter>> GetAllChaptersForSeries(int seriesId);
Task<int> GetAverageUserRating(int chapterId, int userId);
}
public class ChapterRepository : IChapterRepository
{
@ -310,4 +311,20 @@ public class ChapterRepository : IChapterRepository
.ThenInclude(cp => cp.Person)
.ToListAsync();
}
public async Task<int> GetAverageUserRating(int chapterId, int userId)
{
// If there is 0 or 1 rating and that rating is you, return 0 back
var countOfRatingsThatAreUser = await _context.AppUserChapterRating
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
.CountAsync(u => u.AppUserId == userId);
if (countOfRatingsThatAreUser == 1)
{
return 0;
}
var avg = (await _context.AppUserChapterRating
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
.AverageAsync(r => (int?) r.Rating));
return avg.HasValue ? (int) (avg.Value * 20) : 0;
}
}

View file

@ -66,6 +66,7 @@ public interface IUserRepository
Task<bool> IsUserAdminAsync(AppUser? user);
Task<IList<string>> GetRoles(int userId);
Task<AppUserRating?> GetUserRatingAsync(int seriesId, int userId);
Task<AppUserChapterRating?> GetUserChapterRatingAsync(int chapterId, int userId);
Task<IList<UserReviewDto>> GetUserRatingDtosForSeriesAsync(int seriesId, int userId);
Task<IList<UserReviewDto>> GetUserRatingDtosForVolumeAsync(int volumeId, int userId);
Task<IList<UserReviewDto>> GetUserRatingDtosForChapterAsync(int chapterId, int userId);
@ -592,6 +593,12 @@ public class UserRepository : IUserRepository
.Where(r => r.SeriesId == seriesId && r.AppUserId == userId)
.SingleOrDefaultAsync();
}
public async Task<AppUserChapterRating?> GetUserChapterRatingAsync(int chapterId, int userId)
{
return await _context.AppUserChapterRating
.Where(r => r.ChapterId == chapterId && r.AppUserId == userId)
.FirstOrDefaultAsync();
}
public async Task<IList<UserReviewDto>> GetUserRatingDtosForSeriesAsync(int seriesId, int userId)
{

View file

@ -52,6 +52,7 @@ public static class ApplicationServiceExtensions
services.AddScoped<IMediaErrorService, MediaErrorService>();
services.AddScoped<IMediaConversionService, MediaConversionService>();
services.AddScoped<IStreamService, StreamService>();
services.AddScoped<IRatingService, RatingService>();
services.AddScoped<IScannerService, ScannerService>();
services.AddScoped<IProcessSeries, ProcessSeries>();

View file

@ -0,0 +1,63 @@
using System;
using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
using API.DTOs;
using API.Entities;
using Microsoft.Extensions.Logging;
namespace API.Services;
public interface IRatingService
{
Task<bool> UpdateChapterRating(int userId, UpdateChapterRatingDto dto);
}
public class RatingService: IRatingService
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<RatingService> _logger;
public RatingService(IUnitOfWork unitOfWork, ILogger<RatingService> logger)
{
_unitOfWork = unitOfWork;
_logger = logger;
}
public async Task<bool> UpdateChapterRating(int userId, UpdateChapterRatingDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.ChapterRatings);
if (user == null) throw new UnauthorizedAccessException();
var rating = await _unitOfWork.UserRepository.GetUserChapterRatingAsync(dto.ChapterId, userId) ?? new AppUserChapterRating();
rating.Rating = Math.Clamp(dto.Rating, 0, 5);
rating.HasBeenRated = true;
rating.ChapterId = dto.ChapterId;
if (rating.Id == 0)
{
user.ChapterRatings.Add(rating);
}
_unitOfWork.UserRepository.Update(user);
try
{
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
{
// Scrobble Update?
return true;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an exception while updating chapter rating");
}
await _unitOfWork.RollbackAsync();
user.ChapterRatings.Remove(rating);
return false;
}
}