Unify ChapterRating with Rating
This commit is contained in:
parent
a9e2937651
commit
f29c63c6c4
34 changed files with 266 additions and 679 deletions
|
|
@ -939,7 +939,7 @@ public class SeriesFilterTests : AbstractDbTest
|
||||||
var zeroRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2);
|
var zeroRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2);
|
||||||
Assert.NotNull(zeroRating);
|
Assert.NotNull(zeroRating);
|
||||||
|
|
||||||
Assert.True(await seriesService.UpdateRating(user, new UpdateSeriesRatingDto()
|
Assert.True(await seriesService.UpdateRating(user, new UpdateRatingDto()
|
||||||
{
|
{
|
||||||
SeriesId = zeroRating.Id,
|
SeriesId = zeroRating.Id,
|
||||||
UserRating = 0
|
UserRating = 0
|
||||||
|
|
@ -948,7 +948,7 @@ public class SeriesFilterTests : AbstractDbTest
|
||||||
// Select 4.5 Rating
|
// Select 4.5 Rating
|
||||||
var partialRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(3);
|
var partialRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(3);
|
||||||
|
|
||||||
Assert.True(await seriesService.UpdateRating(user, new UpdateSeriesRatingDto()
|
Assert.True(await seriesService.UpdateRating(user, new UpdateRatingDto()
|
||||||
{
|
{
|
||||||
SeriesId = partialRating.Id,
|
SeriesId = partialRating.Id,
|
||||||
UserRating = 4.5f
|
UserRating = 4.5f
|
||||||
|
|
|
||||||
|
|
@ -617,7 +617,7 @@ public class SeriesServiceTests : AbstractDbTest
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings);
|
||||||
|
|
||||||
JobStorage.Current = new InMemoryStorage();
|
JobStorage.Current = new InMemoryStorage();
|
||||||
var result = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto
|
var result = await _seriesService.UpdateRating(user, new UpdateRatingDto
|
||||||
{
|
{
|
||||||
SeriesId = 1,
|
SeriesId = 1,
|
||||||
UserRating = 3,
|
UserRating = 3,
|
||||||
|
|
@ -651,7 +651,7 @@ public class SeriesServiceTests : AbstractDbTest
|
||||||
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings);
|
||||||
|
|
||||||
var result = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto
|
var result = await _seriesService.UpdateRating(user, new UpdateRatingDto
|
||||||
{
|
{
|
||||||
SeriesId = 1,
|
SeriesId = 1,
|
||||||
UserRating = 3,
|
UserRating = 3,
|
||||||
|
|
@ -667,7 +667,7 @@ public class SeriesServiceTests : AbstractDbTest
|
||||||
|
|
||||||
// Update the DB again
|
// Update the DB again
|
||||||
|
|
||||||
var result2 = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto
|
var result2 = await _seriesService.UpdateRating(user, new UpdateRatingDto
|
||||||
{
|
{
|
||||||
SeriesId = 1,
|
SeriesId = 1,
|
||||||
UserRating = 5,
|
UserRating = 5,
|
||||||
|
|
@ -701,7 +701,7 @@ public class SeriesServiceTests : AbstractDbTest
|
||||||
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings);
|
||||||
|
|
||||||
var result = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto
|
var result = await _seriesService.UpdateRating(user, new UpdateRatingDto
|
||||||
{
|
{
|
||||||
SeriesId = 1,
|
SeriesId = 1,
|
||||||
UserRating = 10,
|
UserRating = 10,
|
||||||
|
|
@ -736,7 +736,7 @@ public class SeriesServiceTests : AbstractDbTest
|
||||||
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings);
|
||||||
|
|
||||||
var result = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto
|
var result = await _seriesService.UpdateRating(user, new UpdateRatingDto
|
||||||
{
|
{
|
||||||
SeriesId = 2,
|
SeriesId = 2,
|
||||||
UserRating = 5,
|
UserRating = 5,
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,13 @@ public class ChapterController : BaseApiController
|
||||||
private readonly ILocalizationService _localizationService;
|
private readonly ILocalizationService _localizationService;
|
||||||
private readonly IEventHub _eventHub;
|
private readonly IEventHub _eventHub;
|
||||||
private readonly ILogger<ChapterController> _logger;
|
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;
|
_unitOfWork = unitOfWork;
|
||||||
_localizationService = localizationService;
|
_localizationService = localizationService;
|
||||||
_eventHub = eventHub;
|
_eventHub = eventHub;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_ratingService = ratingService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -406,15 +403,4 @@ public class ChapterController : BaseApiController
|
||||||
return await _unitOfWork.UserRepository.GetUserRatingDtosForChapterAsync(chapterId, User.GetUserId());
|
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using API.Constants;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
using API.Services;
|
||||||
using API.Services.Plus;
|
using API.Services.Plus;
|
||||||
using EasyCaching.Core;
|
using EasyCaching.Core;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
@ -21,31 +22,45 @@ namespace API.Controllers;
|
||||||
public class RatingController : BaseApiController
|
public class RatingController : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
private readonly IRatingService _ratingService;
|
||||||
|
private readonly ILocalizationService _localizationService;
|
||||||
|
|
||||||
public RatingController(IUnitOfWork unitOfWork)
|
public RatingController(IUnitOfWork unitOfWork, IRatingService ratingService, ILocalizationService localizationService)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
|
_ratingService = ratingService;
|
||||||
|
_localizationService = localizationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<ActionResult> UpdateRating(UpdateRatingDto updateRating)
|
||||||
|
{
|
||||||
|
if (await _ratingService.UpdateRating(User.GetUserId(), updateRating))
|
||||||
|
{
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("overall")]
|
[HttpGet("overall")]
|
||||||
public async Task<ActionResult<RatingDto>> GetOverallRating(int seriesId)
|
public async Task<ActionResult<RatingDto>> GetOverallRating(int seriesId, [FromQuery] int? chapterId)
|
||||||
{
|
{
|
||||||
|
int average;
|
||||||
|
if (chapterId != null)
|
||||||
|
{
|
||||||
|
average = await _unitOfWork.ChapterRepository.GetAverageUserRating(chapterId.Value, User.GetUserId());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
average = await _unitOfWork.SeriesRepository.GetAverageUserRating(seriesId, User.GetUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return Ok(new RatingDto()
|
return Ok(new RatingDto()
|
||||||
{
|
{
|
||||||
Provider = ScrobbleProvider.Kavita,
|
Provider = ScrobbleProvider.Kavita,
|
||||||
AverageScore = await _unitOfWork.SeriesRepository.GetAverageUserRating(seriesId, User.GetUserId()),
|
AverageScore = average,
|
||||||
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,
|
FavoriteCount = 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
|
|
@ -29,9 +30,24 @@ public class ReviewController : BaseApiController
|
||||||
_scrobblingService = scrobblingService;
|
_scrobblingService = scrobblingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all reviews for the series, or chapter
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IList<UserReviewDto>> GetReviews([FromQuery] int seriesId, [FromQuery] int? chapterId)
|
||||||
|
{
|
||||||
|
if (chapterId == null)
|
||||||
|
{
|
||||||
|
return await _unitOfWork.UserRepository.GetUserRatingDtosForSeriesAsync(seriesId, User.GetUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _unitOfWork.UserRepository.GetUserRatingDtosForChapterAsync(chapterId.Value, User.GetUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the review for a given series
|
/// Updates the review for a given series, or chapter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dto"></param>
|
/// <param name="dto"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
|
@ -41,7 +57,7 @@ public class ReviewController : BaseApiController
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
var ratingBuilder = new RatingBuilder(user.Ratings.FirstOrDefault(r => r.SeriesId == dto.SeriesId));
|
var ratingBuilder = new RatingBuilder(await _unitOfWork.UserRepository.GetUserRatingAsync(dto.SeriesId, user.Id, dto.ChapterId));
|
||||||
|
|
||||||
var rating = ratingBuilder
|
var rating = ratingBuilder
|
||||||
.WithBody(dto.Body)
|
.WithBody(dto.Body)
|
||||||
|
|
@ -64,78 +80,18 @@ public class ReviewController : BaseApiController
|
||||||
return Ok(_mapper.Map<UserReviewDto>(rating));
|
return Ok(_mapper.Map<UserReviewDto>(rating));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the review for a given series
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dto"></param>
|
|
||||||
/// <param name="chapterId"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpPost("chapter/{chapterId}")]
|
|
||||||
public async Task<ActionResult<UserReviewDto>> UpdateChapterReview(int chapterId, UpdateUserReviewDto dto)
|
|
||||||
{
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.ChapterRatings);
|
|
||||||
if (user == null) return Unauthorized();
|
|
||||||
|
|
||||||
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId, ChapterIncludes.None);
|
|
||||||
if (chapter == null) return BadRequest();
|
|
||||||
|
|
||||||
var builder = new ChapterRatingBuilder(user.ChapterRatings.FirstOrDefault(r => r.SeriesId == dto.SeriesId));
|
|
||||||
|
|
||||||
var rating = builder
|
|
||||||
.WithSeriesId(dto.SeriesId)
|
|
||||||
.WithVolumeId(chapter.VolumeId)
|
|
||||||
.WithChapterId(chapter.Id)
|
|
||||||
.WithRating(dto.Rating)
|
|
||||||
.WithReview(dto.Body)
|
|
||||||
.WithProvider(ScrobbleProvider.Kavita)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
if (rating.Id == 0)
|
|
||||||
{
|
|
||||||
user.ChapterRatings.Add(rating);
|
|
||||||
}
|
|
||||||
_unitOfWork.UserRepository.Update(user);
|
|
||||||
|
|
||||||
await _unitOfWork.CommitAsync();
|
|
||||||
|
|
||||||
// Do I need this?
|
|
||||||
//BackgroundJob.Enqueue(() =>
|
|
||||||
// _scrobblingService.ScrobbleReviewUpdate(user.Id, dto.SeriesId, string.Empty, dto.Body));
|
|
||||||
return Ok(_mapper.Map<UserReviewDto>(rating));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the user's review for the given series
|
/// Deletes the user's review for the given series, or chapter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public async Task<ActionResult> DeleteReview(int seriesId)
|
public async Task<ActionResult> DeleteReview([FromQuery] int seriesId, [FromQuery] int? chapterId)
|
||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
user.Ratings = user.Ratings.Where(r => r.SeriesId != seriesId).ToList();
|
user.Ratings = user.Ratings.Where(r => !(r.SeriesId == seriesId && r.ChapterId == chapterId)).ToList();
|
||||||
|
|
||||||
_unitOfWork.UserRepository.Update(user);
|
|
||||||
|
|
||||||
await _unitOfWork.CommitAsync();
|
|
||||||
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deletes the user's review for a given chapter
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="chapterId"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpDelete("chapter/{chapterId}")]
|
|
||||||
public async Task<IActionResult> DeleteChapterReview(int chapterId)
|
|
||||||
{
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.ChapterRatings);
|
|
||||||
if (user == null) return Unauthorized();
|
|
||||||
|
|
||||||
user.ChapterRatings = user.ChapterRatings.Where(c => c.ChapterId != chapterId).ToList();
|
|
||||||
|
|
||||||
_unitOfWork.UserRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,21 +191,6 @@ public class SeriesController : BaseApiController
|
||||||
return Ok(await _unitOfWork.ChapterRepository.GetChapterMetadataDtoAsync(chapterId));
|
return Ok(await _unitOfWork.ChapterRepository.GetChapterMetadataDtoAsync(chapterId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update the user rating for the given series
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="updateSeriesRatingDto"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpPost("update-rating")]
|
|
||||||
public async Task<ActionResult> UpdateSeriesRating(UpdateSeriesRatingDto updateSeriesRatingDto)
|
|
||||||
{
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Ratings);
|
|
||||||
if (!await _seriesService.UpdateRating(user!, updateSeriesRatingDto))
|
|
||||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the Series
|
/// Updates the Series
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -83,15 +83,4 @@ public class VolumeController : BaseApiController
|
||||||
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns all reviews related to this volume, that is, the union of reviews of this volumes chapters
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="volumeId"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpGet("review")]
|
|
||||||
public async Task<IList<UserReviewDto>> VolumeReviews([FromQuery] int volumeId)
|
|
||||||
{
|
|
||||||
return await _unitOfWork.UserRepository.GetUserRatingDtosForVolumeAsync(volumeId, User.GetUserId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ namespace API.DTOs.SeriesDetail;
|
||||||
public class UpdateUserReviewDto
|
public class UpdateUserReviewDto
|
||||||
{
|
{
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
|
public int? ChapterId { get; set; }
|
||||||
public int Rating { get; set; }
|
public int Rating { get; set; }
|
||||||
public string Body { get; set; }
|
public string Body { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace API.DTOs;
|
|
||||||
|
|
||||||
public class UpdateChapterRatingDto
|
|
||||||
{
|
|
||||||
public int ChapterId { get; init; }
|
|
||||||
public float Rating { get; init; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
namespace API.DTOs;
|
namespace API.DTOs;
|
||||||
|
|
||||||
public class UpdateSeriesRatingDto
|
public class UpdateRatingDto
|
||||||
{
|
{
|
||||||
public int SeriesId { get; init; }
|
public int SeriesId { get; init; }
|
||||||
|
public int? ChapterId { get; init; }
|
||||||
public float UserRating { get; init; }
|
public float UserRating { get; init; }
|
||||||
}
|
}
|
||||||
|
|
@ -40,7 +40,6 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
||||||
public DbSet<MangaFile> MangaFile { get; set; } = null!;
|
public DbSet<MangaFile> MangaFile { get; set; } = null!;
|
||||||
public DbSet<AppUserProgress> AppUserProgresses { get; set; } = null!;
|
public DbSet<AppUserProgress> AppUserProgresses { get; set; } = null!;
|
||||||
public DbSet<AppUserRating> AppUserRating { get; set; } = null!;
|
public DbSet<AppUserRating> AppUserRating { get; set; } = null!;
|
||||||
public DbSet<AppUserChapterRating> AppUserChapterRating { get; set; } = null!;
|
|
||||||
public DbSet<ServerSetting> ServerSetting { get; set; } = null!;
|
public DbSet<ServerSetting> ServerSetting { get; set; } = null!;
|
||||||
public DbSet<AppUserPreferences> AppUserPreferences { get; set; } = null!;
|
public DbSet<AppUserPreferences> AppUserPreferences { get; set; } = null!;
|
||||||
public DbSet<SeriesMetadata> SeriesMetadata { get; set; } = null!;
|
public DbSet<SeriesMetadata> SeriesMetadata { get; set; } = null!;
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace API.Data.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class ChapterRating : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "AppUserChapterRating",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
|
||||||
Rating = table.Column<float>(type: "REAL", nullable: false),
|
|
||||||
HasBeenRated = table.Column<bool>(type: "INTEGER", nullable: false),
|
|
||||||
Review = table.Column<string>(type: "TEXT", nullable: true),
|
|
||||||
Provider = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
Authority = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
SeriesId = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
ChapterId = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
VolumeId = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
AppUserId = table.Column<int>(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);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_AppUserChapterRating_Volume_VolumeId",
|
|
||||||
column: x => x.VolumeId,
|
|
||||||
principalTable: "Volume",
|
|
||||||
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_AppUserChapterRating_VolumeId",
|
|
||||||
table: "AppUserChapterRating",
|
|
||||||
column: "VolumeId");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "AppUserChapterRating");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
namespace API.Data.Migrations
|
namespace API.Data.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(DataContext))]
|
[DbContext(typeof(DataContext))]
|
||||||
[Migration("20250426142952_ChapterRating")]
|
[Migration("20250426173850_ChapterRating")]
|
||||||
partial class ChapterRating
|
partial class ChapterRating
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -198,52 +198,6 @@ namespace API.Data.Migrations
|
||||||
b.ToTable("AppUserBookmark");
|
b.ToTable("AppUserBookmark");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUserChapterRating", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("AppUserId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("Authority")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("ChapterId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("HasBeenRated")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("Provider")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<float>("Rating")
|
|
||||||
.HasColumnType("REAL");
|
|
||||||
|
|
||||||
b.Property<string>("Review")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("SeriesId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("VolumeId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AppUserId");
|
|
||||||
|
|
||||||
b.HasIndex("ChapterId");
|
|
||||||
|
|
||||||
b.HasIndex("SeriesId");
|
|
||||||
|
|
||||||
b.HasIndex("VolumeId");
|
|
||||||
|
|
||||||
b.ToTable("AppUserChapterRating");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUserCollection", b =>
|
modelBuilder.Entity("API.Entities.AppUserCollection", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
@ -599,6 +553,9 @@ namespace API.Data.Migrations
|
||||||
b.Property<int>("AppUserId")
|
b.Property<int>("AppUserId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("ChapterId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("HasBeenRated")
|
b.Property<bool>("HasBeenRated")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|
@ -618,6 +575,8 @@ namespace API.Data.Migrations
|
||||||
|
|
||||||
b.HasIndex("AppUserId");
|
b.HasIndex("AppUserId");
|
||||||
|
|
||||||
|
b.HasIndex("ChapterId");
|
||||||
|
|
||||||
b.HasIndex("SeriesId");
|
b.HasIndex("SeriesId");
|
||||||
|
|
||||||
b.ToTable("AppUserRating");
|
b.ToTable("AppUserRating");
|
||||||
|
|
@ -2667,41 +2626,6 @@ namespace API.Data.Migrations
|
||||||
b.Navigation("AppUser");
|
b.Navigation("AppUser");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUserChapterRating", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
|
||||||
.WithMany("ChapterRatings")
|
|
||||||
.HasForeignKey("AppUserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Entities.Chapter", "Chapter")
|
|
||||||
.WithMany("Ratings")
|
|
||||||
.HasForeignKey("ChapterId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Entities.Series", "Series")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("SeriesId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Entities.Volume", "Volume")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("VolumeId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("AppUser");
|
|
||||||
|
|
||||||
b.Navigation("Chapter");
|
|
||||||
|
|
||||||
b.Navigation("Series");
|
|
||||||
|
|
||||||
b.Navigation("Volume");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUserCollection", b =>
|
modelBuilder.Entity("API.Entities.AppUserCollection", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||||
|
|
@ -2808,6 +2732,10 @@ namespace API.Data.Migrations
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("API.Entities.Chapter", "Chapter")
|
||||||
|
.WithMany("Ratings")
|
||||||
|
.HasForeignKey("ChapterId");
|
||||||
|
|
||||||
b.HasOne("API.Entities.Series", "Series")
|
b.HasOne("API.Entities.Series", "Series")
|
||||||
.WithMany("Ratings")
|
.WithMany("Ratings")
|
||||||
.HasForeignKey("SeriesId")
|
.HasForeignKey("SeriesId")
|
||||||
|
|
@ -2816,6 +2744,8 @@ namespace API.Data.Migrations
|
||||||
|
|
||||||
b.Navigation("AppUser");
|
b.Navigation("AppUser");
|
||||||
|
|
||||||
|
b.Navigation("Chapter");
|
||||||
|
|
||||||
b.Navigation("Series");
|
b.Navigation("Series");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -3416,8 +3346,6 @@ namespace API.Data.Migrations
|
||||||
{
|
{
|
||||||
b.Navigation("Bookmarks");
|
b.Navigation("Bookmarks");
|
||||||
|
|
||||||
b.Navigation("ChapterRatings");
|
|
||||||
|
|
||||||
b.Navigation("Collections");
|
b.Navigation("Collections");
|
||||||
|
|
||||||
b.Navigation("DashboardStreams");
|
b.Navigation("DashboardStreams");
|
||||||
48
API/Data/Migrations/20250426173850_ChapterRating.cs
Normal file
48
API/Data/Migrations/20250426173850_ChapterRating.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ChapterRating : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "ChapterId",
|
||||||
|
table: "AppUserRating",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AppUserRating_ChapterId",
|
||||||
|
table: "AppUserRating",
|
||||||
|
column: "ChapterId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_AppUserRating_Chapter_ChapterId",
|
||||||
|
table: "AppUserRating",
|
||||||
|
column: "ChapterId",
|
||||||
|
principalTable: "Chapter",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_AppUserRating_Chapter_ChapterId",
|
||||||
|
table: "AppUserRating");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_AppUserRating_ChapterId",
|
||||||
|
table: "AppUserRating");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ChapterId",
|
||||||
|
table: "AppUserRating");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -195,52 +195,6 @@ namespace API.Data.Migrations
|
||||||
b.ToTable("AppUserBookmark");
|
b.ToTable("AppUserBookmark");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUserChapterRating", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("AppUserId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("Authority")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("ChapterId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("HasBeenRated")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("Provider")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<float>("Rating")
|
|
||||||
.HasColumnType("REAL");
|
|
||||||
|
|
||||||
b.Property<string>("Review")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("SeriesId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("VolumeId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("AppUserId");
|
|
||||||
|
|
||||||
b.HasIndex("ChapterId");
|
|
||||||
|
|
||||||
b.HasIndex("SeriesId");
|
|
||||||
|
|
||||||
b.HasIndex("VolumeId");
|
|
||||||
|
|
||||||
b.ToTable("AppUserChapterRating");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUserCollection", b =>
|
modelBuilder.Entity("API.Entities.AppUserCollection", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
@ -596,6 +550,9 @@ namespace API.Data.Migrations
|
||||||
b.Property<int>("AppUserId")
|
b.Property<int>("AppUserId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("ChapterId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("HasBeenRated")
|
b.Property<bool>("HasBeenRated")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|
@ -615,6 +572,8 @@ namespace API.Data.Migrations
|
||||||
|
|
||||||
b.HasIndex("AppUserId");
|
b.HasIndex("AppUserId");
|
||||||
|
|
||||||
|
b.HasIndex("ChapterId");
|
||||||
|
|
||||||
b.HasIndex("SeriesId");
|
b.HasIndex("SeriesId");
|
||||||
|
|
||||||
b.ToTable("AppUserRating");
|
b.ToTable("AppUserRating");
|
||||||
|
|
@ -2664,41 +2623,6 @@ namespace API.Data.Migrations
|
||||||
b.Navigation("AppUser");
|
b.Navigation("AppUser");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUserChapterRating", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
|
||||||
.WithMany("ChapterRatings")
|
|
||||||
.HasForeignKey("AppUserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Entities.Chapter", "Chapter")
|
|
||||||
.WithMany("Ratings")
|
|
||||||
.HasForeignKey("ChapterId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Entities.Series", "Series")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("SeriesId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("API.Entities.Volume", "Volume")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("VolumeId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("AppUser");
|
|
||||||
|
|
||||||
b.Navigation("Chapter");
|
|
||||||
|
|
||||||
b.Navigation("Series");
|
|
||||||
|
|
||||||
b.Navigation("Volume");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUserCollection", b =>
|
modelBuilder.Entity("API.Entities.AppUserCollection", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||||
|
|
@ -2805,6 +2729,10 @@ namespace API.Data.Migrations
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("API.Entities.Chapter", "Chapter")
|
||||||
|
.WithMany("Ratings")
|
||||||
|
.HasForeignKey("ChapterId");
|
||||||
|
|
||||||
b.HasOne("API.Entities.Series", "Series")
|
b.HasOne("API.Entities.Series", "Series")
|
||||||
.WithMany("Ratings")
|
.WithMany("Ratings")
|
||||||
.HasForeignKey("SeriesId")
|
.HasForeignKey("SeriesId")
|
||||||
|
|
@ -2813,6 +2741,8 @@ namespace API.Data.Migrations
|
||||||
|
|
||||||
b.Navigation("AppUser");
|
b.Navigation("AppUser");
|
||||||
|
|
||||||
|
b.Navigation("Chapter");
|
||||||
|
|
||||||
b.Navigation("Series");
|
b.Navigation("Series");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -3413,8 +3343,6 @@ namespace API.Data.Migrations
|
||||||
{
|
{
|
||||||
b.Navigation("Bookmarks");
|
b.Navigation("Bookmarks");
|
||||||
|
|
||||||
b.Navigation("ChapterRatings");
|
|
||||||
|
|
||||||
b.Navigation("Collections");
|
b.Navigation("Collections");
|
||||||
|
|
||||||
b.Navigation("DashboardStreams");
|
b.Navigation("DashboardStreams");
|
||||||
|
|
|
||||||
|
|
@ -315,14 +315,14 @@ public class ChapterRepository : IChapterRepository
|
||||||
public async Task<int> GetAverageUserRating(int chapterId, int userId)
|
public async Task<int> GetAverageUserRating(int chapterId, int userId)
|
||||||
{
|
{
|
||||||
// If there is 0 or 1 rating and that rating is you, return 0 back
|
// If there is 0 or 1 rating and that rating is you, return 0 back
|
||||||
var countOfRatingsThatAreUser = await _context.AppUserChapterRating
|
var countOfRatingsThatAreUser = await _context.AppUserRating
|
||||||
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
|
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
|
||||||
.CountAsync(u => u.AppUserId == userId);
|
.CountAsync(u => u.AppUserId == userId);
|
||||||
if (countOfRatingsThatAreUser == 1)
|
if (countOfRatingsThatAreUser == 1)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
var avg = (await _context.AppUserChapterRating
|
var avg = (await _context.AppUserRating
|
||||||
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
|
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
|
||||||
.AverageAsync(r => (int?) r.Rating));
|
.AverageAsync(r => (int?) r.Rating));
|
||||||
return avg.HasValue ? (int) (avg.Value * 20) : 0;
|
return avg.HasValue ? (int) (avg.Value * 20) : 0;
|
||||||
|
|
|
||||||
|
|
@ -758,7 +758,7 @@ public class SeriesRepository : ISeriesRepository
|
||||||
foreach (var s in series)
|
foreach (var s in series)
|
||||||
{
|
{
|
||||||
s.PagesRead = userProgress.Where(p => p.SeriesId == s.Id).Sum(p => p.PagesRead);
|
s.PagesRead = userProgress.Where(p => p.SeriesId == s.Id).Sum(p => p.PagesRead);
|
||||||
var rating = userRatings.SingleOrDefault(r => r.SeriesId == s.Id);
|
var rating = userRatings.SingleOrDefault(r => r.SeriesId == s.Id && r.ChapterId == null);
|
||||||
if (rating != null)
|
if (rating != null)
|
||||||
{
|
{
|
||||||
s.UserRating = rating.Rating;
|
s.UserRating = rating.Rating;
|
||||||
|
|
@ -2177,14 +2177,14 @@ public class SeriesRepository : ISeriesRepository
|
||||||
{
|
{
|
||||||
// If there is 0 or 1 rating and that rating is you, return 0 back
|
// If there is 0 or 1 rating and that rating is you, return 0 back
|
||||||
var countOfRatingsThatAreUser = await _context.AppUserRating
|
var countOfRatingsThatAreUser = await _context.AppUserRating
|
||||||
.Where(r => r.SeriesId == seriesId && r.HasBeenRated)
|
.Where(r => r.SeriesId == seriesId && r.HasBeenRated && r.ChapterId == null)
|
||||||
.CountAsync(u => u.AppUserId == userId);
|
.CountAsync(u => u.AppUserId == userId);
|
||||||
if (countOfRatingsThatAreUser == 1)
|
if (countOfRatingsThatAreUser == 1)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
var avg = (await _context.AppUserRating
|
var avg = (await _context.AppUserRating
|
||||||
.Where(r => r.SeriesId == seriesId && r.HasBeenRated)
|
.Where(r => r.SeriesId == seriesId && r.HasBeenRated && r.ChapterId == null)
|
||||||
.AverageAsync(r => (int?) r.Rating));
|
.AverageAsync(r => (int?) r.Rating));
|
||||||
return avg.HasValue ? (int) (avg.Value * 20) : 0;
|
return avg.HasValue ? (int) (avg.Value * 20) : 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ public enum AppUserIncludes
|
||||||
SideNavStreams = 4096,
|
SideNavStreams = 4096,
|
||||||
ExternalSources = 8192,
|
ExternalSources = 8192,
|
||||||
Collections = 16384, // 2^14
|
Collections = 16384, // 2^14
|
||||||
ChapterRatings = 1 << 15,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IUserRepository
|
public interface IUserRepository
|
||||||
|
|
@ -65,10 +64,8 @@ public interface IUserRepository
|
||||||
Task<IEnumerable<AppUser>> GetAdminUsersAsync();
|
Task<IEnumerable<AppUser>> GetAdminUsersAsync();
|
||||||
Task<bool> IsUserAdminAsync(AppUser? user);
|
Task<bool> IsUserAdminAsync(AppUser? user);
|
||||||
Task<IList<string>> GetRoles(int userId);
|
Task<IList<string>> GetRoles(int userId);
|
||||||
Task<AppUserRating?> GetUserRatingAsync(int seriesId, int userId);
|
Task<AppUserRating?> GetUserRatingAsync(int seriesId, int userId, int? chapterId = null);
|
||||||
Task<AppUserChapterRating?> GetUserChapterRatingAsync(int chapterId, int userId);
|
|
||||||
Task<IList<UserReviewDto>> GetUserRatingDtosForSeriesAsync(int seriesId, 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);
|
Task<IList<UserReviewDto>> GetUserRatingDtosForChapterAsync(int chapterId, int userId);
|
||||||
Task<AppUserPreferences?> GetPreferencesAsync(string username);
|
Task<AppUserPreferences?> GetPreferencesAsync(string username);
|
||||||
Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForSeries(int userId, int seriesId);
|
Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForSeries(int userId, int seriesId);
|
||||||
|
|
@ -587,36 +584,18 @@ public class UserRepository : IUserRepository
|
||||||
return await _userManager.GetRolesAsync(user);
|
return await _userManager.GetRolesAsync(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AppUserRating?> GetUserRatingAsync(int seriesId, int userId)
|
public async Task<AppUserRating?> GetUserRatingAsync(int seriesId, int userId, int? chapterId = null)
|
||||||
{
|
{
|
||||||
return await _context.AppUserRating
|
return await _context.AppUserRating
|
||||||
.Where(r => r.SeriesId == seriesId && r.AppUserId == userId)
|
.Where(r => r.SeriesId == seriesId && r.AppUserId == userId && r.ChapterId == chapterId)
|
||||||
.SingleOrDefaultAsync();
|
.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)
|
public async Task<IList<UserReviewDto>> GetUserRatingDtosForSeriesAsync(int seriesId, int userId)
|
||||||
{
|
{
|
||||||
return await _context.AppUserRating
|
return await _context.AppUserRating
|
||||||
.Include(r => r.AppUser)
|
.Include(r => r.AppUser)
|
||||||
.Where(r => r.SeriesId == seriesId)
|
.Where(r => r.SeriesId == seriesId && r.ChapterId == null)
|
||||||
.Where(r => r.AppUser.UserPreferences.ShareReviews || r.AppUserId == userId)
|
|
||||||
.OrderBy(r => r.AppUserId == userId)
|
|
||||||
.ThenBy(r => r.Rating)
|
|
||||||
.AsSplitQuery()
|
|
||||||
.ProjectTo<UserReviewDto>(_mapper.ConfigurationProvider)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
public async Task<IList<UserReviewDto>> GetUserRatingDtosForVolumeAsync(int volumeId, int userId)
|
|
||||||
{
|
|
||||||
return await _context.AppUserChapterRating
|
|
||||||
.Include(r => r.AppUser)
|
|
||||||
.Where(r => r.VolumeId == volumeId)
|
|
||||||
.Where(r => r.AppUser.UserPreferences.ShareReviews || r.AppUserId == userId)
|
.Where(r => r.AppUser.UserPreferences.ShareReviews || r.AppUserId == userId)
|
||||||
.OrderBy(r => r.AppUserId == userId)
|
.OrderBy(r => r.AppUserId == userId)
|
||||||
.ThenBy(r => r.Rating)
|
.ThenBy(r => r.Rating)
|
||||||
|
|
@ -627,7 +606,7 @@ public class UserRepository : IUserRepository
|
||||||
|
|
||||||
public async Task<IList<UserReviewDto>> GetUserRatingDtosForChapterAsync(int chapterId, int userId)
|
public async Task<IList<UserReviewDto>> GetUserRatingDtosForChapterAsync(int chapterId, int userId)
|
||||||
{
|
{
|
||||||
return await _context.AppUserChapterRating
|
return await _context.AppUserRating
|
||||||
.Include(r => r.AppUser)
|
.Include(r => r.AppUser)
|
||||||
.Where(r => r.ChapterId == chapterId)
|
.Where(r => r.ChapterId == chapterId)
|
||||||
.Where(r => r.AppUser.UserPreferences.ShareReviews || r.AppUserId == userId)
|
.Where(r => r.AppUser.UserPreferences.ShareReviews || r.AppUserId == userId)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ public class AppUser : IdentityUser<int>, IHasConcurrencyToken
|
||||||
public ICollection<AppUserRole> UserRoles { get; set; } = null!;
|
public ICollection<AppUserRole> UserRoles { get; set; } = null!;
|
||||||
public ICollection<AppUserProgress> Progresses { get; set; } = null!;
|
public ICollection<AppUserProgress> Progresses { get; set; } = null!;
|
||||||
public ICollection<AppUserRating> Ratings { get; set; } = null!;
|
public ICollection<AppUserRating> Ratings { get; set; } = null!;
|
||||||
public ICollection<AppUserChapterRating> ChapterRatings { get; set; } = null!;
|
|
||||||
public AppUserPreferences UserPreferences { get; set; } = null!;
|
public AppUserPreferences UserPreferences { get; set; } = null!;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bookmarks associated with this User
|
/// Bookmarks associated with this User
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
using API.Entities.Enums;
|
|
||||||
using API.Services.Plus;
|
|
||||||
|
|
||||||
namespace API.Entities;
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public enum ReviewAuthority
|
|
||||||
{
|
|
||||||
User = 0,
|
|
||||||
Critic = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AppUserChapterRating
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public float Rating { get; set; }
|
|
||||||
public bool HasBeenRated { get; set; }
|
|
||||||
public string? Review { get; set; }
|
|
||||||
public ScrobbleProvider Provider { get; set; } = ScrobbleProvider.Kavita;
|
|
||||||
public ReviewAuthority Authority { get; set; } = ReviewAuthority.User;
|
|
||||||
|
|
||||||
public int SeriesId { get; set; }
|
|
||||||
public Series Series { get; set; } = null!;
|
|
||||||
|
|
||||||
public int ChapterId { get; set; }
|
|
||||||
public Chapter Chapter { get; set; } = null!;
|
|
||||||
|
|
||||||
public int VolumeId { get; set; }
|
|
||||||
public Volume Volume { get; set; } = null!;
|
|
||||||
|
|
||||||
public int AppUserId { get; set; }
|
|
||||||
public AppUser AppUser { get; set; } = null!;
|
|
||||||
}
|
|
||||||
|
|
@ -26,6 +26,9 @@ public class AppUserRating
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
public Series Series { get; set; } = null!;
|
public Series Series { get; set; } = null!;
|
||||||
|
|
||||||
|
public int? ChapterId { get; set; } = null;
|
||||||
|
public Chapter? Chapter { get; set; } = null;
|
||||||
|
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
public int AppUserId { get; set; }
|
public int AppUserId { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<Genre> Genres { get; set; } = new List<Genre>();
|
public ICollection<Genre> Genres { get; set; } = new List<Genre>();
|
||||||
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
|
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
|
||||||
public ICollection<AppUserChapterRating> Ratings { get; set; } = [];
|
public ICollection<AppUserRating> Ratings { get; set; } = [];
|
||||||
|
|
||||||
public ICollection<AppUserProgress> UserProgress { get; set; }
|
public ICollection<AppUserProgress> UserProgress { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
7
API/Entities/Enums/RatingAuthority.cs
Normal file
7
API/Entities/Enums/RatingAuthority.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace API.Entities.Enums;
|
||||||
|
|
||||||
|
public enum RatingAuthority
|
||||||
|
{
|
||||||
|
User = 0,
|
||||||
|
Critic = 1,
|
||||||
|
}
|
||||||
|
|
@ -253,11 +253,6 @@ public static class IncludesExtensions
|
||||||
.ThenInclude(c => c.Items);
|
.ThenInclude(c => c.Items);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeFlags.HasFlag(AppUserIncludes.ChapterRatings))
|
|
||||||
{
|
|
||||||
query = query.Include(u => u.ChapterRatings);
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.AsSplitQuery();
|
return query.AsSplitQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,22 +97,6 @@ public class AutoMapperProfiles : Profile
|
||||||
.ForMember(dest => dest.Username,
|
.ForMember(dest => dest.Username,
|
||||||
opt =>
|
opt =>
|
||||||
opt.MapFrom(src => src.AppUser.UserName));
|
opt.MapFrom(src => src.AppUser.UserName));
|
||||||
CreateMap<AppUserChapterRating, UserReviewDto>()
|
|
||||||
.ForMember(dest => dest.LibraryId,
|
|
||||||
opt =>
|
|
||||||
opt.MapFrom(src => src.Series.LibraryId))
|
|
||||||
.ForMember(dest => dest.VolumeId,
|
|
||||||
opt =>
|
|
||||||
opt.MapFrom(src => src.VolumeId))
|
|
||||||
.ForMember(dest => dest.ChapterId,
|
|
||||||
opt =>
|
|
||||||
opt.MapFrom(src => src.ChapterId))
|
|
||||||
.ForMember(dest => dest.Body,
|
|
||||||
opt =>
|
|
||||||
opt.MapFrom(src => src.Review))
|
|
||||||
.ForMember(dest => dest.Username,
|
|
||||||
opt =>
|
|
||||||
opt.MapFrom(src => src.AppUser.UserName));
|
|
||||||
|
|
||||||
CreateMap<AppUserProgress, ProgressDto>()
|
CreateMap<AppUserProgress, ProgressDto>()
|
||||||
.ForMember(dest => dest.PageNum,
|
.ForMember(dest => dest.PageNum,
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
using System;
|
|
||||||
using API.Entities;
|
|
||||||
using API.Entities.Enums;
|
|
||||||
using API.Services.Plus;
|
|
||||||
|
|
||||||
namespace API.Helpers.Builders;
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
public class ChapterRatingBuilder
|
|
||||||
{
|
|
||||||
private readonly AppUserChapterRating _rating;
|
|
||||||
|
|
||||||
public AppUserChapterRating Build() => _rating;
|
|
||||||
|
|
||||||
public ChapterRatingBuilder(AppUserChapterRating? rating = null)
|
|
||||||
{
|
|
||||||
_rating = rating ?? new AppUserChapterRating();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChapterRatingBuilder WithSeriesId(int seriesId)
|
|
||||||
{
|
|
||||||
_rating.SeriesId = seriesId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChapterRatingBuilder WithChapterId(int chapterId)
|
|
||||||
{
|
|
||||||
_rating.ChapterId = chapterId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChapterRatingBuilder WithVolumeId(int volumeId)
|
|
||||||
{
|
|
||||||
_rating.VolumeId = volumeId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChapterRatingBuilder WithRating(int rating)
|
|
||||||
{
|
|
||||||
_rating.Rating = Math.Clamp(rating, 0, 5);
|
|
||||||
_rating.HasBeenRated = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChapterRatingBuilder WithReview(string review)
|
|
||||||
{
|
|
||||||
_rating.Review = review;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChapterRatingBuilder WithProvider(ScrobbleProvider provider)
|
|
||||||
{
|
|
||||||
_rating.Provider = provider;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +1,81 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
using API.Services.Plus;
|
||||||
|
using Hangfire;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Services;
|
namespace API.Services;
|
||||||
|
|
||||||
public interface IRatingService
|
public interface IRatingService
|
||||||
{
|
{
|
||||||
Task<bool> UpdateChapterRating(int userId, UpdateChapterRatingDto dto);
|
Task<bool> UpdateRating(int userId, UpdateRatingDto updateRatingDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RatingService: IRatingService
|
public class RatingService: IRatingService
|
||||||
{
|
{
|
||||||
|
|
||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
private readonly IScrobblingService _scrobblingService;
|
||||||
private readonly ILogger<RatingService> _logger;
|
private readonly ILogger<RatingService> _logger;
|
||||||
|
|
||||||
public RatingService(IUnitOfWork unitOfWork, ILogger<RatingService> logger)
|
public RatingService(IUnitOfWork unitOfWork, IScrobblingService scrobblingService, ILogger<RatingService> logger)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
|
_scrobblingService = scrobblingService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateChapterRating(int userId, UpdateChapterRatingDto dto)
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="updateRatingDto"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> UpdateRating(int userId, UpdateRatingDto updateRatingDto)
|
||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.ChapterRatings);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.Ratings);
|
||||||
if (user == null) throw new UnauthorizedAccessException();
|
if (user == null) throw new UnauthorizedAccessException();
|
||||||
|
|
||||||
var rating = await _unitOfWork.UserRepository.GetUserChapterRatingAsync(dto.ChapterId, userId) ?? new AppUserChapterRating();
|
var userRating =
|
||||||
|
await _unitOfWork.UserRepository.GetUserRatingAsync(updateRatingDto.SeriesId, user.Id, updateRatingDto.ChapterId) ??
|
||||||
|
new AppUserRating();
|
||||||
|
|
||||||
rating.Rating = Math.Clamp(dto.Rating, 0, 5);
|
try
|
||||||
rating.HasBeenRated = true;
|
|
||||||
rating.ChapterId = dto.ChapterId;
|
|
||||||
|
|
||||||
if (rating.Id == 0)
|
|
||||||
{
|
{
|
||||||
user.ChapterRatings.Add(rating);
|
userRating.Rating = Math.Clamp(updateRatingDto.UserRating, 0f, 5f);
|
||||||
|
userRating.HasBeenRated = true;
|
||||||
|
userRating.SeriesId = updateRatingDto.SeriesId;
|
||||||
|
userRating.ChapterId = updateRatingDto.ChapterId;
|
||||||
|
|
||||||
|
if (userRating.Id == 0)
|
||||||
|
{
|
||||||
|
user.Ratings ??= new List<AppUserRating>();
|
||||||
|
user.Ratings.Add(userRating);
|
||||||
}
|
}
|
||||||
|
|
||||||
_unitOfWork.UserRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
|
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
|
||||||
{
|
{
|
||||||
// Scrobble Update?
|
BackgroundJob.Enqueue(() =>
|
||||||
|
_scrobblingService.ScrobbleRatingUpdate(user.Id, updateRatingDto.SeriesId,
|
||||||
|
userRating.Rating));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "There was an exception while updating chapter rating");
|
_logger.LogError(ex, "There was an exception saving rating");
|
||||||
}
|
}
|
||||||
|
|
||||||
await _unitOfWork.RollbackAsync();
|
await _unitOfWork.RollbackAsync();
|
||||||
user.ChapterRatings.Remove(rating);
|
user.Ratings?.Remove(userRating);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,11 @@ public interface ISeriesService
|
||||||
{
|
{
|
||||||
Task<SeriesDetailDto> GetSeriesDetail(int seriesId, int userId);
|
Task<SeriesDetailDto> GetSeriesDetail(int seriesId, int userId);
|
||||||
Task<bool> UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto);
|
Task<bool> UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto);
|
||||||
Task<bool> UpdateRating(AppUser user, UpdateSeriesRatingDto updateSeriesRatingDto);
|
|
||||||
Task<bool> DeleteMultipleSeries(IList<int> seriesIds);
|
Task<bool> DeleteMultipleSeries(IList<int> seriesIds);
|
||||||
Task<bool> UpdateRelatedSeries(UpdateRelatedSeriesDto dto);
|
Task<bool> UpdateRelatedSeries(UpdateRelatedSeriesDto dto);
|
||||||
Task<RelatedSeriesDto> GetRelatedSeries(int userId, int seriesId);
|
Task<RelatedSeriesDto> GetRelatedSeries(int userId, int seriesId);
|
||||||
Task<string> FormatChapterTitle(int userId, ChapterDto chapter, LibraryType libraryType, bool withHash = true);
|
Task<string> FormatChapterTitle(int userId, ChapterDto chapter, LibraryType libraryType, bool withHash = true);
|
||||||
Task<string> FormatChapterTitle(int userId, Chapter chapter, LibraryType libraryType, bool withHash = true);
|
Task<string> FormatChapterTitle(int userId, Chapter chapter, LibraryType libraryType, bool withHash = true);
|
||||||
|
|
||||||
Task<string> FormatChapterTitle(int userId, bool isSpecial, LibraryType libraryType, string chapterRange, string? chapterTitle,
|
Task<string> FormatChapterTitle(int userId, bool isSpecial, LibraryType libraryType, string chapterRange, string? chapterTitle,
|
||||||
bool withHash);
|
bool withHash);
|
||||||
Task<string> FormatChapterName(int userId, LibraryType libraryType, bool withHash = false);
|
Task<string> FormatChapterName(int userId, LibraryType libraryType, bool withHash = false);
|
||||||
|
|
@ -447,57 +445,6 @@ public class SeriesService : ISeriesService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user">User with Ratings includes</param>
|
|
||||||
/// <param name="updateSeriesRatingDto"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<bool> UpdateRating(AppUser? user, UpdateSeriesRatingDto updateSeriesRatingDto)
|
|
||||||
{
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
_logger.LogError("Cannot update rating of null user");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var userRating =
|
|
||||||
await _unitOfWork.UserRepository.GetUserRatingAsync(updateSeriesRatingDto.SeriesId, user.Id) ??
|
|
||||||
new AppUserRating();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
userRating.Rating = Math.Clamp(updateSeriesRatingDto.UserRating, 0f, 5f);
|
|
||||||
userRating.HasBeenRated = true;
|
|
||||||
userRating.SeriesId = updateSeriesRatingDto.SeriesId;
|
|
||||||
|
|
||||||
if (userRating.Id == 0)
|
|
||||||
{
|
|
||||||
user.Ratings ??= new List<AppUserRating>();
|
|
||||||
user.Ratings.Add(userRating);
|
|
||||||
}
|
|
||||||
|
|
||||||
_unitOfWork.UserRepository.Update(user);
|
|
||||||
|
|
||||||
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
|
|
||||||
{
|
|
||||||
BackgroundJob.Enqueue(() =>
|
|
||||||
_scrobblingService.ScrobbleRatingUpdate(user.Id, updateSeriesRatingDto.SeriesId,
|
|
||||||
userRating.Rating));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "There was an exception saving rating");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _unitOfWork.RollbackAsync();
|
|
||||||
user.Ratings?.Remove(userRating);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> DeleteMultipleSeries(IList<int> seriesIds)
|
public async Task<bool> DeleteMultipleSeries(IList<int> seriesIds)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -35,20 +35,4 @@ export class ChapterService {
|
||||||
return this.httpClient.get<Array<UserReview>>(this.baseUrl + 'chapter/review?chapterId='+chapterId);
|
return this.httpClient.get<Array<UserReview>>(this.baseUrl + 'chapter/review?chapterId='+chapterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChapterReview(seriesId: number, chapterId: number, body: string, rating: number) {
|
|
||||||
return this.httpClient.post<UserReview>(this.baseUrl + 'review/chapter/'+chapterId, {seriesId, rating, body});
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteChapterReview(chapterId: number) {
|
|
||||||
return this.httpClient.delete(this.baseUrl + 'review/chapter/'+chapterId);
|
|
||||||
}
|
|
||||||
|
|
||||||
overallRating(chapterId: number) {
|
|
||||||
return this.httpClient.get<Rating>(this.baseUrl + 'rating/overall?chapterId='+chapterId);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRating(chapterId: number, rating: number) {
|
|
||||||
return this.httpClient.post(this.baseUrl + 'chapter/update-rating', {chapterId, rating});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
UI/Web/src/app/_services/review.service.ts
Normal file
56
UI/Web/src/app/_services/review.service.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {UserReview} from "../_single-module/review-card/user-review";
|
||||||
|
import {environment} from "../../environments/environment";
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {Rating} from "../_models/rating";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ReviewService {
|
||||||
|
|
||||||
|
private baseUrl = environment.apiUrl;
|
||||||
|
|
||||||
|
constructor(private httpClient: HttpClient) { }
|
||||||
|
|
||||||
|
getReviews(seriesId: number, chapterId?: number) {
|
||||||
|
if (chapterId) {
|
||||||
|
return this.httpClient.get<UserReview[]>(this.baseUrl + `review?chapterId=${chapterId}&seriesId=${seriesId}`);
|
||||||
|
}
|
||||||
|
return this.httpClient.get<UserReview[]>(this.baseUrl + 'review?seriesId=' + seriesId);
|
||||||
|
}
|
||||||
|
|
||||||
|
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?seriesId=' + seriesId);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateReview(seriesId: number, body: string, rating: number, chapterId?: number) {
|
||||||
|
if (chapterId) {
|
||||||
|
return this.httpClient.post<UserReview>(this.baseUrl + `review?chapterId=${chapterId}&seriesId=${seriesId}`, {
|
||||||
|
rating, body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.httpClient.post<UserReview>(this.baseUrl + 'review', {
|
||||||
|
seriesId, rating, body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRating(seriesId: number, userRating: number, chapterId?: number) {
|
||||||
|
return this.httpClient.post(this.baseUrl + 'rating', {
|
||||||
|
seriesId, chapterId, userRating
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
overallRating(seriesId: number, chapterId?: number) {
|
||||||
|
if (chapterId) {
|
||||||
|
return this.httpClient.get<Rating>(this.baseUrl + `rating/overall?chapterId=${chapterId}&seriesId=${seriesId}`);
|
||||||
|
}
|
||||||
|
return this.httpClient.get<Rating>(this.baseUrl + 'rating/overall?seriesId=' + seriesId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -203,27 +203,9 @@ export class SeriesService {
|
||||||
return this.httpClient.get<SeriesDetail>(this.baseUrl + 'series/series-detail?seriesId=' + seriesId);
|
return this.httpClient.get<SeriesDetail>(this.baseUrl + 'series/series-detail?seriesId=' + seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
deleteReview(seriesId: number) {
|
|
||||||
return this.httpClient.delete(this.baseUrl + 'review?seriesId=' + seriesId);
|
|
||||||
}
|
|
||||||
updateReview(seriesId: number, body: string, rating: number) {
|
|
||||||
return this.httpClient.post<UserReview>(this.baseUrl + 'review', {
|
|
||||||
seriesId, rating, body
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getReviews(seriesId: number) {
|
|
||||||
return this.httpClient.get<Array<UserReview>>(this.baseUrl + 'review?seriesId=' + seriesId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRatings(seriesId: number) {
|
getRatings(seriesId: number) {
|
||||||
return this.httpClient.get<Array<Rating>>(this.baseUrl + 'rating?seriesId=' + seriesId);
|
return this.httpClient.get<Array<Rating>>(this.baseUrl + 'rating?seriesId=' + seriesId);
|
||||||
}
|
}
|
||||||
getOverallRating(seriesId: number) {
|
|
||||||
return this.httpClient.get<Rating>(this.baseUrl + 'rating/overall?seriesId=' + seriesId);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFromOnDeck(seriesId: number) {
|
removeFromOnDeck(seriesId: number) {
|
||||||
return this.httpClient.post(this.baseUrl + 'series/remove-from-on-deck?seriesId=' + seriesId, {});
|
return this.httpClient.post(this.baseUrl + 'series/remove-from-on-deck?seriesId=' + seriesId, {});
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {ChapterService} from "../../_services/chapter.service";
|
||||||
import {of} from "rxjs";
|
import {of} from "rxjs";
|
||||||
import {NgxStarsModule} from "ngx-stars";
|
import {NgxStarsModule} from "ngx-stars";
|
||||||
import {ThemeService} from "../../_services/theme.service";
|
import {ThemeService} from "../../_services/theme.service";
|
||||||
|
import {ReviewService} from "../../_services/review.service";
|
||||||
|
|
||||||
export enum ReviewModalCloseAction {
|
export enum ReviewModalCloseAction {
|
||||||
Create,
|
Create,
|
||||||
|
|
@ -33,8 +34,8 @@ export interface ReviewModalCloseEvent {
|
||||||
export class ReviewModalComponent implements OnInit {
|
export class ReviewModalComponent implements OnInit {
|
||||||
|
|
||||||
protected readonly modal = inject(NgbActiveModal);
|
protected readonly modal = inject(NgbActiveModal);
|
||||||
|
private readonly reviewService = inject(ReviewService);
|
||||||
private readonly seriesService = inject(SeriesService);
|
private readonly seriesService = inject(SeriesService);
|
||||||
private readonly chapterService = inject(ChapterService);
|
|
||||||
private readonly cdRef = inject(ChangeDetectorRef);
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
private readonly confirmService = inject(ConfirmService);
|
private readonly confirmService = inject(ConfirmService);
|
||||||
private readonly toastr = inject(ToastrService);
|
private readonly toastr = inject(ToastrService);
|
||||||
|
|
@ -66,14 +67,7 @@ export class ReviewModalComponent implements OnInit {
|
||||||
async delete() {
|
async delete() {
|
||||||
if (!await this.confirmService.confirm(translate('toasts.delete-review'))) return;
|
if (!await this.confirmService.confirm(translate('toasts.delete-review'))) return;
|
||||||
|
|
||||||
let obs;
|
this.reviewService.deleteReview(this.review.seriesId, this.review.chapterId).subscribe(() => {
|
||||||
if (!this.review.chapterId) {
|
|
||||||
obs = this.seriesService.deleteReview(this.review.seriesId);
|
|
||||||
} else {
|
|
||||||
obs = this.chapterService.deleteChapterReview(this.review.chapterId)
|
|
||||||
}
|
|
||||||
|
|
||||||
obs?.subscribe(() => {
|
|
||||||
this.toastr.success(translate('toasts.review-deleted'));
|
this.toastr.success(translate('toasts.review-deleted'));
|
||||||
this.modal.close({success: true, review: this.review, action: ReviewModalCloseAction.Delete});
|
this.modal.close({success: true, review: this.review, action: ReviewModalCloseAction.Delete});
|
||||||
});
|
});
|
||||||
|
|
@ -85,14 +79,7 @@ export class ReviewModalComponent implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let obs;
|
this.reviewService.updateReview(this.review.seriesId, model.reviewBody, this.rating, this.review.chapterId).subscribe(review => {
|
||||||
if (!this.review.chapterId) {
|
|
||||||
obs = this.seriesService.updateReview(this.review.seriesId, model.reviewBody, this.rating);
|
|
||||||
} else {
|
|
||||||
obs = this.chapterService.updateChapterReview(this.review.seriesId, this.review.chapterId, model.reviewBody, this.rating);
|
|
||||||
}
|
|
||||||
|
|
||||||
obs?.subscribe(review => {
|
|
||||||
this.modal.close({success: true, review: review, action: ReviewModalCloseAction.Edit});
|
this.modal.close({success: true, review: review, action: ReviewModalCloseAction.Edit});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import {AsyncPipe, NgOptimizedImage, NgTemplateOutlet} from "@angular/common";
|
||||||
import {RatingModalComponent} from "../rating-modal/rating-modal.component";
|
import {RatingModalComponent} from "../rating-modal/rating-modal.component";
|
||||||
import {ScrobbleProviderNamePipe} from "../../../_pipes/scrobble-provider-name.pipe";
|
import {ScrobbleProviderNamePipe} from "../../../_pipes/scrobble-provider-name.pipe";
|
||||||
import {ChapterService} from "../../../_services/chapter.service";
|
import {ChapterService} from "../../../_services/chapter.service";
|
||||||
|
import {ReviewService} from "../../../_services/review.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-external-rating',
|
selector: 'app-external-rating',
|
||||||
|
|
@ -38,8 +39,7 @@ import {ChapterService} from "../../../_services/chapter.service";
|
||||||
export class ExternalRatingComponent implements OnInit {
|
export class ExternalRatingComponent implements OnInit {
|
||||||
|
|
||||||
private readonly cdRef = inject(ChangeDetectorRef);
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
private readonly seriesService = inject(SeriesService);
|
private readonly reviewService = inject(ReviewService);
|
||||||
private readonly chapterService = inject(ChapterService);
|
|
||||||
private readonly themeService = inject(ThemeService);
|
private readonly themeService = inject(ThemeService);
|
||||||
public readonly utilityService = inject(UtilityService);
|
public readonly utilityService = inject(UtilityService);
|
||||||
public readonly destroyRef = inject(DestroyRef);
|
public readonly destroyRef = inject(DestroyRef);
|
||||||
|
|
@ -61,24 +61,13 @@ export class ExternalRatingComponent implements OnInit {
|
||||||
starColor = this.themeService.getCssVariable('--rating-star-color');
|
starColor = this.themeService.getCssVariable('--rating-star-color');
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
let obs;
|
this.reviewService.overallRating(this.seriesId, this.chapterId).subscribe(r => {
|
||||||
if (this.chapterId) {
|
this.overallRating = r.averageScore;
|
||||||
obs = this.chapterService.overallRating(this.chapterId);
|
});
|
||||||
} else {
|
|
||||||
obs = this.seriesService.getOverallRating(this.seriesId);
|
|
||||||
}
|
|
||||||
obs?.subscribe(r => this.overallRating = r.averageScore);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRating(rating: number) {
|
updateRating(rating: number) {
|
||||||
let obs;
|
this.reviewService.updateRating(this.seriesId, rating, this.chapterId).subscribe(() => {
|
||||||
if (this.chapterId) {
|
|
||||||
obs = this.chapterService.updateRating(this.chapterId, rating);
|
|
||||||
} else {
|
|
||||||
obs = this.seriesService.updateRating(this.seriesId, rating);
|
|
||||||
}
|
|
||||||
|
|
||||||
obs?.subscribe(() => {
|
|
||||||
this.userRating = rating;
|
this.userRating = rating;
|
||||||
this.hasUserRated = true;
|
this.hasUserRated = true;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ import {UserReview} from "../_single-module/review-card/user-review";
|
||||||
import {ReviewsComponent} from "../_single-module/reviews/reviews.component";
|
import {ReviewsComponent} from "../_single-module/reviews/reviews.component";
|
||||||
import {ExternalRatingComponent} from "../series-detail/_components/external-rating/external-rating.component";
|
import {ExternalRatingComponent} from "../series-detail/_components/external-rating/external-rating.component";
|
||||||
import {User} from "../_models/user";
|
import {User} from "../_models/user";
|
||||||
|
import {ReviewService} from "../_services/review.service";
|
||||||
|
|
||||||
enum TabID {
|
enum TabID {
|
||||||
|
|
||||||
|
|
@ -182,6 +183,7 @@ export class VolumeDetailComponent implements OnInit {
|
||||||
private readonly readingListService = inject(ReadingListService);
|
private readonly readingListService = inject(ReadingListService);
|
||||||
private readonly messageHub = inject(MessageHubService);
|
private readonly messageHub = inject(MessageHubService);
|
||||||
private readonly location = inject(Location);
|
private readonly location = inject(Location);
|
||||||
|
private readonly reviewService = inject(ReviewService);
|
||||||
|
|
||||||
|
|
||||||
protected readonly AgeRating = AgeRating;
|
protected readonly AgeRating = AgeRating;
|
||||||
|
|
@ -391,7 +393,6 @@ export class VolumeDetailComponent implements OnInit {
|
||||||
series: this.seriesService.getSeries(this.seriesId),
|
series: this.seriesService.getSeries(this.seriesId),
|
||||||
volume: this.volumeService.getVolumeMetadata(this.volumeId),
|
volume: this.volumeService.getVolumeMetadata(this.volumeId),
|
||||||
libraryType: this.libraryService.getLibraryType(this.libraryId),
|
libraryType: this.libraryService.getLibraryType(this.libraryId),
|
||||||
reviews: this.volumeService.volumeReviews(this.volumeId),
|
|
||||||
}).subscribe(results => {
|
}).subscribe(results => {
|
||||||
|
|
||||||
if (results.volume === null) {
|
if (results.volume === null) {
|
||||||
|
|
@ -402,8 +403,13 @@ export class VolumeDetailComponent implements OnInit {
|
||||||
this.series = results.series;
|
this.series = results.series;
|
||||||
this.volume = results.volume;
|
this.volume = results.volume;
|
||||||
this.libraryType = results.libraryType;
|
this.libraryType = results.libraryType;
|
||||||
this.userReviews = results.reviews.filter(r => !r.isExternal);
|
|
||||||
this.plusReviews = results.reviews.filter(r => r.isExternal);
|
if (this.volume.chapters.length === 1) {
|
||||||
|
this.reviewService.getReviews(this.seriesId, this.volume.chapters[0].id).subscribe(reviews => {
|
||||||
|
this.userReviews = reviews.filter(r => !r.isExternal);
|
||||||
|
this.plusReviews = reviews.filter(r => r.isExternal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.themeService.setColorScape(this.volume!.primaryColor, this.volume!.secondaryColor);
|
this.themeService.setColorScape(this.volume!.primaryColor, this.volume!.secondaryColor);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue