Unify ChapterRating with Rating

This commit is contained in:
Amelia 2025-04-26 20:20:14 +02:00
parent a9e2937651
commit f29c63c6c4
No known key found for this signature in database
GPG key ID: D6D0ECE365407EAA
34 changed files with 266 additions and 679 deletions

View file

@ -40,7 +40,6 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
public DbSet<MangaFile> MangaFile { get; set; } = null!;
public DbSet<AppUserProgress> AppUserProgresses { 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<AppUserPreferences> AppUserPreferences { get; set; } = null!;
public DbSet<SeriesMetadata> SeriesMetadata { get; set; } = null!;

View file

@ -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");
}
}
}

View file

@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace API.Data.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250426142952_ChapterRating")]
[Migration("20250426173850_ChapterRating")]
partial class ChapterRating
{
/// <inheritdoc />
@ -198,52 +198,6 @@ namespace API.Data.Migrations
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 =>
{
b.Property<int>("Id")
@ -599,6 +553,9 @@ namespace API.Data.Migrations
b.Property<int>("AppUserId")
.HasColumnType("INTEGER");
b.Property<int?>("ChapterId")
.HasColumnType("INTEGER");
b.Property<bool>("HasBeenRated")
.HasColumnType("INTEGER");
@ -618,6 +575,8 @@ namespace API.Data.Migrations
b.HasIndex("AppUserId");
b.HasIndex("ChapterId");
b.HasIndex("SeriesId");
b.ToTable("AppUserRating");
@ -2667,41 +2626,6 @@ namespace API.Data.Migrations
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 =>
{
b.HasOne("API.Entities.AppUser", "AppUser")
@ -2808,6 +2732,10 @@ namespace API.Data.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("API.Entities.Chapter", "Chapter")
.WithMany("Ratings")
.HasForeignKey("ChapterId");
b.HasOne("API.Entities.Series", "Series")
.WithMany("Ratings")
.HasForeignKey("SeriesId")
@ -2816,6 +2744,8 @@ namespace API.Data.Migrations
b.Navigation("AppUser");
b.Navigation("Chapter");
b.Navigation("Series");
});
@ -3416,8 +3346,6 @@ namespace API.Data.Migrations
{
b.Navigation("Bookmarks");
b.Navigation("ChapterRatings");
b.Navigation("Collections");
b.Navigation("DashboardStreams");

View 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");
}
}
}

View file

@ -195,52 +195,6 @@ namespace API.Data.Migrations
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 =>
{
b.Property<int>("Id")
@ -596,6 +550,9 @@ namespace API.Data.Migrations
b.Property<int>("AppUserId")
.HasColumnType("INTEGER");
b.Property<int?>("ChapterId")
.HasColumnType("INTEGER");
b.Property<bool>("HasBeenRated")
.HasColumnType("INTEGER");
@ -615,6 +572,8 @@ namespace API.Data.Migrations
b.HasIndex("AppUserId");
b.HasIndex("ChapterId");
b.HasIndex("SeriesId");
b.ToTable("AppUserRating");
@ -2664,41 +2623,6 @@ namespace API.Data.Migrations
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 =>
{
b.HasOne("API.Entities.AppUser", "AppUser")
@ -2805,6 +2729,10 @@ namespace API.Data.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("API.Entities.Chapter", "Chapter")
.WithMany("Ratings")
.HasForeignKey("ChapterId");
b.HasOne("API.Entities.Series", "Series")
.WithMany("Ratings")
.HasForeignKey("SeriesId")
@ -2813,6 +2741,8 @@ namespace API.Data.Migrations
b.Navigation("AppUser");
b.Navigation("Chapter");
b.Navigation("Series");
});
@ -3413,8 +3343,6 @@ namespace API.Data.Migrations
{
b.Navigation("Bookmarks");
b.Navigation("ChapterRatings");
b.Navigation("Collections");
b.Navigation("DashboardStreams");

View file

@ -315,14 +315,14 @@ public class ChapterRepository : IChapterRepository
public async Task<int> GetAverageUserRating(int chapterId, int userId)
{
// If there is 0 or 1 rating and that rating is you, return 0 back
var countOfRatingsThatAreUser = await _context.AppUserChapterRating
var countOfRatingsThatAreUser = await _context.AppUserRating
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
.CountAsync(u => u.AppUserId == userId);
if (countOfRatingsThatAreUser == 1)
{
return 0;
}
var avg = (await _context.AppUserChapterRating
var avg = (await _context.AppUserRating
.Where(r => r.ChapterId == chapterId && r.HasBeenRated)
.AverageAsync(r => (int?) r.Rating));
return avg.HasValue ? (int) (avg.Value * 20) : 0;

View file

@ -758,7 +758,7 @@ public class SeriesRepository : ISeriesRepository
foreach (var s in series)
{
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)
{
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
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);
if (countOfRatingsThatAreUser == 1)
{
return 0;
}
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));
return avg.HasValue ? (int) (avg.Value * 20) : 0;
}

View file

@ -43,7 +43,6 @@ public enum AppUserIncludes
SideNavStreams = 4096,
ExternalSources = 8192,
Collections = 16384, // 2^14
ChapterRatings = 1 << 15,
}
public interface IUserRepository
@ -65,10 +64,8 @@ public interface IUserRepository
Task<IEnumerable<AppUser>> GetAdminUsersAsync();
Task<bool> IsUserAdminAsync(AppUser? user);
Task<IList<string>> GetRoles(int userId);
Task<AppUserRating?> GetUserRatingAsync(int seriesId, int userId);
Task<AppUserChapterRating?> GetUserChapterRatingAsync(int chapterId, int userId);
Task<AppUserRating?> GetUserRatingAsync(int seriesId, int userId, int? chapterId = null);
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<AppUserPreferences?> GetPreferencesAsync(string username);
Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForSeries(int userId, int seriesId);
@ -587,36 +584,18 @@ public class UserRepository : IUserRepository
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
.Where(r => r.SeriesId == seriesId && r.AppUserId == userId)
.Where(r => r.SeriesId == seriesId && r.AppUserId == userId && r.ChapterId == chapterId)
.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)
{
return await _context.AppUserRating
.Include(r => r.AppUser)
.Where(r => r.SeriesId == seriesId)
.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.SeriesId == seriesId && r.ChapterId == null)
.Where(r => r.AppUser.UserPreferences.ShareReviews || r.AppUserId == userId)
.OrderBy(r => r.AppUserId == userId)
.ThenBy(r => r.Rating)
@ -627,7 +606,7 @@ public class UserRepository : IUserRepository
public async Task<IList<UserReviewDto>> GetUserRatingDtosForChapterAsync(int chapterId, int userId)
{
return await _context.AppUserChapterRating
return await _context.AppUserRating
.Include(r => r.AppUser)
.Where(r => r.ChapterId == chapterId)
.Where(r => r.AppUser.UserPreferences.ShareReviews || r.AppUserId == userId)