Simply entities & seperate endpoints
This commit is contained in:
parent
41faa30e6f
commit
6d4dfcda67
22 changed files with 299 additions and 615 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ public class ChapterController : BaseApiController
|
|||
|
||||
|
||||
[HttpGet("chapter-detail-plus")]
|
||||
public async Task<ChapterDetailPlusDto> ChapterDetailPlus([FromQuery] int chapterId)
|
||||
public async Task<ActionResult<ChapterDetailPlusDto>> 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<UserReviewDto>(ex)).ToList();
|
||||
userReviews.AddRange(ReviewHelper.SelectSpectrumOfReviews(dtos));
|
||||
userReviews.AddRange(ReviewHelper.SelectSpectrumOfReviews(externalReviews));
|
||||
}
|
||||
|
||||
ret.Reviews = userReviews;
|
||||
|
|
|
|||
|
|
@ -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<ActionResult> UpdateRating(UpdateRatingDto updateRating)
|
||||
/// <summary>
|
||||
/// Update the users' rating of the given series
|
||||
/// </summary>
|
||||
/// <param name="updateRating"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="UnauthorizedAccessException"></exception>
|
||||
[HttpPost("series")]
|
||||
public async Task<ActionResult> 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<ActionResult<RatingDto>> GetOverallRating(int seriesId, [FromQuery] int? chapterId)
|
||||
/// <summary>
|
||||
/// Update the users' rating of the given chapter
|
||||
/// </summary>
|
||||
/// <param name="updateRating">chapterId must be set</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="UnauthorizedAccessException"></exception>
|
||||
[HttpPost("chapter")]
|
||||
public async Task<ActionResult> 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<ActionResult<RatingDto>> 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<ActionResult<RatingDto>> GetOverallChapterRating(int chapterId)
|
||||
{
|
||||
return Ok(new RatingDto()
|
||||
{
|
||||
Provider = ScrobbleProvider.Kavita,
|
||||
AverageScore = await _unitOfWork.ChapterRepository.GetAverageUserRating(chapterId, User.GetUserId()),
|
||||
FavoriteCount = 0,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,39 +33,16 @@ public class ReviewController : BaseApiController
|
|||
|
||||
|
||||
/// <summary>
|
||||
/// Updates the review for a given series, or chapter
|
||||
/// Updates the user's review for a given series
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<UserReviewDto>> UpdateReview(UpdateUserReviewDto dto)
|
||||
[HttpPost("series")]
|
||||
public async Task<ActionResult<UserReviewDto>> 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<UserReviewDto> 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<UserReviewDto>(rating);
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
BackgroundJob.Enqueue(() =>
|
||||
_scrobblingService.ScrobbleReviewUpdate(user.Id, dto.SeriesId, string.Empty, dto.Body));
|
||||
return Ok(_mapper.Map<UserReviewDto>(rating));
|
||||
}
|
||||
|
||||
private async Task<UserReviewDto> UpdateChapterReview(AppUser user, UpdateUserReviewDto dto, int chapterId)
|
||||
/// <summary>
|
||||
/// Update the user's review for a given chapter
|
||||
/// </summary>
|
||||
/// <param name="dto">chapterId must be set</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("chapter")]
|
||||
public async Task<ActionResult<UserReviewDto>> 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,29 +92,46 @@ public class ReviewController : BaseApiController
|
|||
{
|
||||
user.ChapterRatings.Add(rating);
|
||||
}
|
||||
return _mapper.Map<UserReviewDto>(rating);
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
return Ok(_mapper.Map<UserReviewDto>(rating));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the user's review for the given series, or chapter
|
||||
/// Deletes the user's review for the given series
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpDelete]
|
||||
public async Task<ActionResult> DeleteReview([FromQuery] int seriesId, [FromQuery] int? chapterId)
|
||||
[HttpDelete("series")]
|
||||
public async Task<ActionResult> 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();
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the user's review for the given chapter
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("chapter")]
|
||||
public async Task<ActionResult> 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);
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
|
|
|||
|
|
@ -79,8 +79,6 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
|||
public DbSet<MetadataSettings> MetadataSettings { get; set; } = null!;
|
||||
public DbSet<MetadataFieldMapping> MetadataFieldMapping { get; set; } = null!;
|
||||
public DbSet<AppUserChapterRating> AppUserChapterRating { get; set; } = null!;
|
||||
public DbSet<ExternalChapterReview> ExternalChapterReview { get; set; } = null!;
|
||||
public DbSet<ExternalChapterMetadata> ExternalChapterMetadata { get; set; } = null!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,159 +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),
|
||||
SeriesId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ChapterId = 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);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExternalChapterMetadata",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ChapterId = table.Column<int>(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<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Tagline = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Body = table.Column<string>(type: "TEXT", nullable: true),
|
||||
BodyJustText = table.Column<string>(type: "TEXT", nullable: true),
|
||||
RawBody = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Provider = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Authority = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
SiteUrl = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Username = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Rating = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Score = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
TotalVotes = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ChapterId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ExternalChapterReview", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExternalChapterMetadataExternalChapterReview",
|
||||
columns: table => new
|
||||
{
|
||||
ExternalChapterMetadatasId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ExternalReviewsId = table.Column<int>(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");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AppUserChapterRating");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExternalChapterMetadataExternalChapterReview");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExternalChapterMetadata");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExternalChapterReview");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -1348,70 +1348,6 @@ namespace API.Data.Migrations
|
|||
b.ToTable("MediaError");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalChapterMetadata", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChapterId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ExternalChapterMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalChapterReview", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Authority")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Body")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BodyJustText")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Provider")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RawBody")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Score")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SiteUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Tagline")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TotalVotes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ExternalChapterReview");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
@ -1481,12 +1417,18 @@ namespace API.Data.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Authority")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Body")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BodyJustText")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("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<int>("ExternalChapterMetadatasId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ExternalReviewsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ExternalChapterMetadatasId", "ExternalReviewsId");
|
||||
|
||||
b.HasIndex("ExternalReviewsId");
|
||||
|
||||
b.ToTable("ExternalChapterMetadataExternalChapterReview");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b =>
|
||||
{
|
||||
b.Property<int>("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");
|
||||
|
||||
113
API/Data/Migrations/20250428180534_ChapterRating.cs
Normal file
113
API/Data/Migrations/20250428180534_ChapterRating.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
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: "Authority",
|
||||
table: "ExternalReview",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "ChapterId",
|
||||
table: "ExternalReview",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
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),
|
||||
SeriesId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ChapterId = 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);
|
||||
});
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1345,70 +1345,6 @@ namespace API.Data.Migrations
|
|||
b.ToTable("MediaError");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalChapterMetadata", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChapterId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ExternalChapterMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalChapterReview", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Authority")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Body")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BodyJustText")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Provider")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RawBody")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Score")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SiteUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Tagline")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TotalVotes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ExternalChapterReview");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
@ -1478,12 +1414,18 @@ namespace API.Data.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Authority")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Body")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("BodyJustText")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("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<int>("ExternalChapterMetadatasId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ExternalReviewsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("ExternalChapterMetadatasId", "ExternalReviewsId");
|
||||
|
||||
b.HasIndex("ExternalReviewsId");
|
||||
|
||||
b.ToTable("ExternalChapterMetadataExternalChapterReview");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b =>
|
||||
{
|
||||
b.Property<int>("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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Chapter> GetChaptersForSeries(int seriesId);
|
||||
Task<IList<Chapter>> GetAllChaptersForSeries(int seriesId);
|
||||
Task<int> GetAverageUserRating(int chapterId, int userId);
|
||||
Task<IList<UserReviewDto>> 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<IList<UserReviewDto>> 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<UserReviewDto>(r))
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ExternalChapterReview>? reviews);
|
||||
|
||||
Task<ExternalChapterMetadata?> 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<ExternalChapterReview>? reviews)
|
||||
{
|
||||
if (reviews == null) return;
|
||||
context.ExternalChapterReview.RemoveRange(reviews);
|
||||
|
||||
}
|
||||
public async Task<ExternalChapterMetadata?> Get(int chapterId, ExternalChapterMetadataIncludes includes = ExternalChapterMetadataIncludes.ExternalReviews)
|
||||
{
|
||||
return await context.ExternalChapterMetadata
|
||||
.Includes(includes)
|
||||
.FirstOrDefaultAsync(c => c.ChapterId == chapterId);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,6 @@ public interface IUnitOfWork
|
|||
IAppUserExternalSourceRepository AppUserExternalSourceRepository { get; }
|
||||
IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository { get; }
|
||||
IEmailHistoryRepository EmailHistoryRepository { get; }
|
||||
IExternalChapterMetadataRepository ExternalChapterMetadataRepository { get; }
|
||||
bool Commit();
|
||||
Task<bool> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -105,7 +103,6 @@ public class UnitOfWork : IUnitOfWork
|
|||
public IAppUserExternalSourceRepository AppUserExternalSourceRepository { get; }
|
||||
public IExternalSeriesMetadataRepository ExternalSeriesMetadataRepository { get; }
|
||||
public IEmailHistoryRepository EmailHistoryRepository { get; }
|
||||
public IExternalChapterMetadataRepository ExternalChapterMetadataRepository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Commits changes to the DB. Completes the open transaction.
|
||||
|
|
|
|||
|
|
@ -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<ExternalReview> ExternalReviews { get; set; } = [];
|
||||
|
||||
public void UpdateFrom(ParserInfo info)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.Entities.Metadata;
|
||||
|
||||
/// <summary>
|
||||
/// External Metadata from Kavita+ for a Chapter
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As apposed to <see cref="ExternalSeriesMetadata"/>,
|
||||
/// we do not have a ValidUntilUtc, as this is only matched together with the series.
|
||||
/// </remarks>
|
||||
public class ExternalChapterMetadata
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int ChapterId { get; set; }
|
||||
|
||||
public ICollection<ExternalChapterReview> ExternalReviews { get; set; } = null!;
|
||||
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using API.Entities.Enums;
|
||||
using API.Services.Plus;
|
||||
|
||||
namespace API.Entities.Metadata;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Externally supplied Review for a given Series
|
||||
/// </summary>
|
||||
public class ExternalChapterReview
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Tagline { get; set; }
|
||||
public required string Body { get; set; }
|
||||
/// <summary>
|
||||
/// Pure text version of the body
|
||||
/// </summary>
|
||||
public required string BodyJustText { get; set; }
|
||||
/// <summary>
|
||||
/// Raw from the provider. Usually Markdown
|
||||
/// </summary>
|
||||
public string RawBody { get; set; }
|
||||
public required ScrobbleProvider Provider { get; set; }
|
||||
public RatingAuthority Authority { get; set; } = RatingAuthority.User;
|
||||
public string SiteUrl { get; set; }
|
||||
/// <summary>
|
||||
/// Reviewer's username
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
/// <summary>
|
||||
/// An Optional Rating coming from the Review
|
||||
/// </summary>
|
||||
public int Rating { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// The media's overall Score
|
||||
/// </summary>
|
||||
public int Score { get; set; }
|
||||
public int TotalVotes { get; set; }
|
||||
|
||||
public int ChapterId { get; set; }
|
||||
|
||||
// Relationships
|
||||
public ICollection<ExternalChapterMetadata> ExternalChapterMetadatas { get; set; } = null!;
|
||||
|
||||
}
|
||||
|
|
@ -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
|
|||
/// </summary>
|
||||
public string RawBody { get; set; }
|
||||
public required ScrobbleProvider Provider { get; set; }
|
||||
public RatingAuthority Authority { get; set; } = RatingAuthority.User;
|
||||
public string SiteUrl { get; set; }
|
||||
/// <summary>
|
||||
/// Reviewer's username
|
||||
|
|
@ -37,6 +39,7 @@ public class ExternalReview
|
|||
|
||||
|
||||
public int SeriesId { get; set; }
|
||||
public int? ChapterId { get; set; }
|
||||
|
||||
// Relationships
|
||||
public ICollection<ExternalSeriesMetadata> ExternalSeriesMetadatas { get; set; } = null!;
|
||||
|
|
|
|||
|
|
@ -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<ExternalChapterMetadata> Includes(this IQueryable<ExternalChapterMetadata> query, ExternalChapterMetadataIncludes includeFlags)
|
||||
{
|
||||
if (includeFlags.HasFlag(ExternalChapterMetadataIncludes.ExternalReviews))
|
||||
{
|
||||
query = query.Include(e => e.ExternalReviews);
|
||||
}
|
||||
|
||||
return query.AsSplitQuery();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -344,19 +344,11 @@ public class AutoMapperProfiles : Profile
|
|||
.ForMember(dest => dest.IsExternal,
|
||||
opt =>
|
||||
opt.MapFrom(src => true));
|
||||
CreateMap<ExternalChapterReview, UserReviewDto>()
|
||||
.ForMember(dest => dest.IsExternal,
|
||||
opt =>
|
||||
opt.MapFrom(src => true));
|
||||
|
||||
CreateMap<UserReviewDto, ExternalReview>()
|
||||
.ForMember(dest => dest.BodyJustText,
|
||||
opt =>
|
||||
opt.MapFrom(src => ReviewHelper.GetCharacters(src.Body)));
|
||||
CreateMap<UserReviewDto, ExternalChapterReview>()
|
||||
.ForMember(dest => dest.BodyJustText,
|
||||
opt =>
|
||||
opt.MapFrom(src => ReviewHelper.GetCharacters(src.Body)));
|
||||
|
||||
CreateMap<ExternalRecommendation, ExternalSeriesDto>();
|
||||
CreateMap<Series, ManageMatchSeriesDto>()
|
||||
|
|
|
|||
|
|
@ -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<ExternalChapterReview> externalReviews = [];
|
||||
List<ExternalReview> externalReviews = [];
|
||||
|
||||
externalReviews.AddRange(metadata.CriticReviews
|
||||
.Where(r => !string.IsNullOrWhiteSpace(r.Username) && !string.IsNullOrWhiteSpace(r.Body))
|
||||
.Select(r =>
|
||||
{
|
||||
var review = _mapper.Map<ExternalChapterReview>(r);
|
||||
var review = _mapper.Map<ExternalReview>(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<ExternalChapterReview>(r);
|
||||
var review = _mapper.Map<ExternalReview>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets from DB or creates a new one with just ChapterId
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <param name="chapter"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<ExternalChapterMetadata> 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<RecommendationDto> ProcessRecommendations(LibraryType libraryType, IEnumerable<MediaRecommendationDto> recs,
|
||||
ExternalSeriesMetadata externalSeriesMetadata)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,12 +14,20 @@ namespace API.Services;
|
|||
public interface IRatingService
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// Updates the users' rating for a given series
|
||||
/// </summary>
|
||||
/// <param name="user">Should include ratings</param>
|
||||
/// <param name="updateRatingDto"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> UpdateRating(AppUser user, UpdateRatingDto updateRatingDto);
|
||||
Task<bool> UpdateSeriesRating(AppUser user, UpdateRatingDto updateRatingDto);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the users' rating for a given chapter
|
||||
/// </summary>
|
||||
/// <param name="user">Should include ratings</param>
|
||||
/// <param name="updateRatingDto">chapterId must be set</param>
|
||||
/// <returns></returns>
|
||||
Task<bool> UpdateChapterRating(AppUser user, UpdateRatingDto updateRatingDto);
|
||||
}
|
||||
|
||||
public class RatingService: IRatingService
|
||||
|
|
@ -36,17 +44,7 @@ public class RatingService: IRatingService
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateRating(AppUser user, UpdateRatingDto updateRatingDto)
|
||||
{
|
||||
if (updateRatingDto.ChapterId != null)
|
||||
{
|
||||
return await UpdateChapterRating(user, updateRatingDto);
|
||||
}
|
||||
|
||||
return await UpdateSeriesRating(user, updateRatingDto);
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateSeriesRating(AppUser user, UpdateRatingDto updateRatingDto)
|
||||
public async Task<bool> 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<bool> UpdateChapterRating(AppUser user, UpdateRatingDto updateRatingDto)
|
||||
public async Task<bool> UpdateChapterRating(AppUser user, UpdateRatingDto updateRatingDto)
|
||||
{
|
||||
if (updateRatingDto.ChapterId == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<UserReview>(this.baseUrl + `review`, {
|
||||
return this.httpClient.post<UserReview>(this.baseUrl + `review/chapter`, {
|
||||
seriesId, chapterId, body
|
||||
});
|
||||
}
|
||||
|
||||
return this.httpClient.post<UserReview>(this.baseUrl + 'review', {
|
||||
return this.httpClient.post<UserReview>(this.baseUrl + 'review/series', {
|
||||
seriesId, body
|
||||
});
|
||||
}
|
||||
|
||||
updateRating(seriesId: number, userRating: number, chapterId?: number) {
|
||||
return this.httpClient.post(this.baseUrl + 'rating', {
|
||||
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<Rating>(this.baseUrl + `rating/overall?chapterId=${chapterId}&seriesId=${seriesId}`);
|
||||
return this.httpClient.get<Rating>(this.baseUrl + `rating/overall-chapter?chapterId=${chapterId}`);
|
||||
}
|
||||
return this.httpClient.get<Rating>(this.baseUrl + 'rating/overall?seriesId=' + seriesId);
|
||||
|
||||
return this.httpClient.get<Rating>(this.baseUrl + `rating/overall-series?seriesId=${seriesId}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue