diff --git a/API.Tests/Services/EntityNamingServiceTests.cs b/API.Tests/Services/EntityNamingServiceTests.cs
index b26d366bc..683d71b92 100644
--- a/API.Tests/Services/EntityNamingServiceTests.cs
+++ b/API.Tests/Services/EntityNamingServiceTests.cs
@@ -1081,5 +1081,125 @@ public class EntityNamingServiceTests
Assert.DoesNotContain(" - ", result.Replace("Chapter ", ""));
}
+ #region Extra Tests
+
+ [Fact]
+ public void BuildChapterTitle_ManualTest1()
+ {
+ var chapterDto = new ChapterDto
+ {
+ Id = 2002,
+ Number = Parser.LooseLeafVolume,
+ Range = Parser.LooseLeafVolume,
+ MinNumber = Parser.LooseLeafVolumeNumber,
+ MaxNumber = Parser.LooseLeafVolumeNumber,
+ SortOrder = Parser.LooseLeafVolumeNumber,
+ IsSpecial = false,
+ Title = Parser.LooseLeafVolume,
+ TitleName = "The Vexations of a Shut-In Vampire Princess, Vol. 4",
+ Pages = 40,
+ PagesRead = 20,
+ CoverImageLocked = false,
+ VolumeId = 1446,
+ };
+
+ var volumeDto = new VolumeDto
+ {
+ Id = 1446,
+ Number = 4,
+ Name = "4",
+ MinNumber = 4,
+ MaxNumber = 4,
+ Pages = 40,
+ PagesRead = 20,
+ SeriesId = 256,
+ Chapters = [chapterDto],
+ };
+
+ var chapterTitle = _sut.BuildChapterTitle(LibraryType.LightNovel, volumeDto, chapterDto);
+ Assert.Equal("The Vexations of a Shut-In Vampire Princess, Vol. 4", chapterTitle);
+ }
+
+ [Fact]
+ public void BuildChapterTitle_ManualTest2()
+ {
+ var chapterDto = new ChapterDto
+ {
+ Number = Parser.LooseLeafVolume,
+ Range = Parser.LooseLeafVolume,
+ Title = Parser.LooseLeafVolume,
+ TitleName = "Accel World, Vol. 5: The Floating Starlight Bridge",
+ MinNumber = Parser.LooseLeafVolumeNumber,
+ MaxNumber = Parser.LooseLeafVolumeNumber,
+ IsSpecial = false,
+ };
+
+ var volumeDto = new VolumeDto
+ {
+ Number = 5,
+ Name = "5",
+ MinNumber = 5,
+ MaxNumber = 5,
+ Chapters = [chapterDto],
+ };
+
+ var chapterTitle = _sut.BuildChapterTitle(LibraryType.LightNovel, volumeDto, chapterDto);
+ Assert.Equal("Accel World, Vol. 5: The Floating Starlight Bridge", chapterTitle);
+ }
+
+ [Fact]
+ public void BuildChapterTitle_ManualTest3()
+ {
+ var chapterDto = new ChapterDto
+ {
+ Number = Parser.LooseLeafVolume,
+ Range = "After Sundown",
+ Title = "After Sundown",
+ MinNumber = Parser.LooseLeafVolumeNumber,
+ MaxNumber = Parser.LooseLeafVolumeNumber,
+ IsSpecial = true,
+ };
+
+ var volumeDto = new VolumeDto
+ {
+ Number = Parser.SpecialVolumeNumber,
+ Name = Parser.SpecialVolumeNumber.ToString(),
+ MinNumber = Parser.SpecialVolumeNumber,
+ MaxNumber = Parser.SpecialVolumeNumber,
+ Chapters = [chapterDto],
+ };
+
+ var chapterTitle = _sut.BuildChapterTitle(LibraryType.Book, volumeDto, chapterDto);
+ Assert.Equal("After Sundown", chapterTitle);
+ }
+
+ [Fact]
+ public void BuildChapterTitle_ManualTest4()
+ {
+ var chapterDto = new ChapterDto
+ {
+ Number = Parser.LooseLeafVolume,
+ Range = "A Girl on the Shore (Umibe no Onnanoko)",
+ Title = "A Girl on the Shore (Umibe no Onnanoko)",
+ MinNumber = Parser.LooseLeafVolumeNumber,
+ MaxNumber = Parser.LooseLeafVolumeNumber,
+ IsSpecial = true,
+ };
+
+ var volumeDto = new VolumeDto
+ {
+ Number = Parser.SpecialVolumeNumber,
+ Name = Parser.SpecialVolumeNumber.ToString(),
+ MinNumber = Parser.SpecialVolumeNumber,
+ MaxNumber = Parser.SpecialVolumeNumber,
+ Chapters = [chapterDto],
+ };
+
+ var chapterTitle = _sut.BuildChapterTitle(LibraryType.Manga, volumeDto, chapterDto);
+ Assert.Equal("A Girl on the Shore (Umibe no Onnanoko)", chapterTitle);
+ }
+
+ #endregion
+
}
diff --git a/API/Controllers/ManageController.cs b/API/Controllers/ManageController.cs
index a57e76aea..0469f5a32 100644
--- a/API/Controllers/ManageController.cs
+++ b/API/Controllers/ManageController.cs
@@ -1,10 +1,13 @@
-using System;
+#nullable enable
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using API.Constants;
using API.Data;
using API.DTOs;
using API.DTOs.KavitaPlus.Manage;
+using API.Extensions;
+using API.Helpers;
using API.Services.Plus;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -32,10 +35,15 @@ public class ManageController : BaseApiController
///
[Authorize(PolicyGroups.AdminPolicy)]
[HttpPost("series-metadata")]
- public async Task>> SeriesMetadata(ManageMatchFilterDto filter)
+ public async Task>> SeriesMetadata(ManageMatchFilterDto filter, [FromQuery] UserParams? userParams)
{
- if (!await _licenseService.HasActiveLicense()) return Ok(Array.Empty());
+ //if (!await _licenseService.HasActiveLicense()) return Ok(Array.Empty());
- return Ok(await _unitOfWork.ExternalSeriesMetadataRepository.GetAllSeries(filter));
+ userParams ??= UserParams.Default;
+
+ var res = await _unitOfWork.ExternalSeriesMetadataRepository.GetAllSeries(filter, userParams);
+
+ Response.AddPaginationHeader(res);
+ return Ok(res);
}
}
diff --git a/API/Controllers/StatsController.cs b/API/Controllers/StatsController.cs
index 8405a0020..b10a182db 100644
--- a/API/Controllers/StatsController.cs
+++ b/API/Controllers/StatsController.cs
@@ -215,29 +215,31 @@ public class StatsController(
///
/// Returns a count of pages read per year for a given userId.
///
- /// If userId is 0 and user is not an admin, API will default to userId
+ ///
///
+ [ProfilePrivacy]
[HttpGet("pages-per-year")]
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Statistics)]
- public async Task>>> GetPagesReadPerYear(int userId = 0)
+ public async Task>>> GetPagesReadPerYear(int? userId)
{
- var isAdmin = User.IsInRole(PolicyConstants.AdminRole);
- if (!isAdmin) userId = await unitOfWork.UserRepository.GetUserIdByUsernameAsync(Username!);
- return Ok(await statService.GetPagesReadCountByYear(userId));
+ userId ??= UserId;
+
+ return Ok(await statService.GetPagesReadCountByYear(userId.Value));
}
///
/// Returns a count of words read per year for a given userId.
///
- /// If userId is 0 and user is not an admin, API will default to userId
+ ///
///
+ [ProfilePrivacy]
[HttpGet("words-per-year")]
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Statistics)]
- public async Task>>> GetWordsReadPerYear(int userId = 0)
+ public async Task>>> GetWordsReadPerYear(int? userId)
{
- var isAdmin = User.IsInRole(PolicyConstants.AdminRole);
- if (!isAdmin) userId = await unitOfWork.UserRepository.GetUserIdByUsernameAsync(Username!);
- return Ok(statService.GetWordsReadCountByYear(userId));
+ userId ??= UserId;
+
+ return Ok(await statService.GetWordsReadCountByYear(userId.Value));
}
[HttpGet("files-added-over-time")]
@@ -453,10 +455,9 @@ public class StatsController(
///
///
[HttpGet("reading-history")]
- [ProfilePrivacy]
- public async Task>> GetReadingHistoryItems([FromQuery] int userId, [FromQuery] StatsFilterDto filter, [FromQuery] UserParams userParams)
+ public async Task>> GetReadingHistoryItems([FromQuery] StatsFilterDto filter, [FromQuery] UserParams userParams)
{
- var result = await statService.GetReadingHistoryItems(filter, userParams, userId, UserId);
+ var result = await statService.GetReadingHistoryItems(filter, userParams, UserId, UserId);
Response.AddPaginationHeader(result.CurrentPage, result.PageSize, result.TotalCount, result.TotalPages);
diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs
index c7f00bd5d..c3ceb92f4 100644
--- a/API/Controllers/UsersController.cs
+++ b/API/Controllers/UsersController.cs
@@ -56,9 +56,6 @@ public class UsersController : BaseApiController
_unitOfWork.UserRepository.Delete(user);
- //(TODO: After updating a role or removing a user, delete their token)
- // await _userManager.RemoveAuthenticationTokenAsync(user, TokenOptions.DefaultProvider, RefreshTokenName);
-
if (await _unitOfWork.CommitAsync()) return Ok();
return BadRequest(await _localizationService.Translate(UserId, "generic-user-delete"));
@@ -92,6 +89,18 @@ public class UsersController : BaseApiController
return Ok(_mapper.Map(user));
}
+ ///
+ /// Does the requested user have their profile sharing on
+ ///
+ ///
+ ///
+ [HttpGet("has-profile-shared")]
+ [Authorize]
+ public async Task> HasProfileShared(int userId)
+ {
+ var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
+ return Ok(user?.UserPreferences.SocialPreferences.ShareProfile);
+ }
[HttpGet("has-reading-progress")]
public async Task> HasReadingProgress(int libraryId)
diff --git a/API/DTOs/Account/AuthKeyDto.cs b/API/DTOs/Account/AuthKeyDto.cs
index f56bb7f41..4f2625cbf 100644
--- a/API/DTOs/Account/AuthKeyDto.cs
+++ b/API/DTOs/Account/AuthKeyDto.cs
@@ -21,7 +21,7 @@ public sealed record AuthKeyDto
/// An Optional time which the Key expires
///
public DateTime? ExpiresAtUtc { get; set; }
- public DateTime? LastAccessedAt { get; set; }
+ public DateTime? LastAccessedAtUtc { get; set; }
///
/// Kavita will have a short-lived key
diff --git a/API/Data/Migrations/20260110164419_AppUserAuthKeyUtcMissing.Designer.cs b/API/Data/Migrations/20260110164419_AppUserAuthKeyUtcMissing.Designer.cs
new file mode 100644
index 000000000..302d41386
--- /dev/null
+++ b/API/Data/Migrations/20260110164419_AppUserAuthKeyUtcMissing.Designer.cs
@@ -0,0 +1,4482 @@
+//
+using System;
+using System.Collections.Generic;
+using API.Data;
+using API.Entities.MetadataMatching;
+using API.Entities.Progress;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace API.Data.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20260110164419_AppUserAuthKeyUtcMissing")]
+ partial class AppUserAuthKeyUtcMissing
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.1");
+
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRestriction")
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRestrictionIncludeUnknowns")
+ .HasColumnType("INTEGER");
+
+ b.Property("AniListAccessToken")
+ .HasColumnType("TEXT");
+
+ b.Property("ApiKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("ConfirmationToken")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("HasRunScrobbleEventGeneration")
+ .HasColumnType("INTEGER");
+
+ b.Property("IdentityProvider")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0);
+
+ b.Property("LastActive")
+ .HasColumnType("TEXT");
+
+ b.Property("LastActiveUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("MalAccessToken")
+ .HasColumnType("TEXT");
+
+ b.Property("MalUserName")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("OidcId")
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("PrimaryColor")
+ .HasColumnType("TEXT");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("ScrobbleEventGenerationRan")
+ .HasColumnType("TEXT");
+
+ b.Property("SecondaryColor")
+ .HasColumnType("TEXT");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserAnnotation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Comment")
+ .HasColumnType("TEXT");
+
+ b.Property("CommentHtml")
+ .HasColumnType("TEXT");
+
+ b.Property("CommentPlainText")
+ .HasColumnType("TEXT");
+
+ b.Property("ContainsSpoiler")
+ .HasColumnType("INTEGER");
+
+ b.Property("Context")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("EndingXPath")
+ .HasColumnType("TEXT");
+
+ b.Property("HighlightCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("Likes")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("[]");
+
+ b.Property("PageNumber")
+ .HasColumnType("INTEGER");
+
+ b.Property("SelectedSlotIndex")
+ .HasColumnType("INTEGER");
+
+ b.Property("SelectedText")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.Property("XPath")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("ChapterId");
+
+ b.HasIndex("LibraryId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserAnnotation");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserBookmark", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("FileName")
+ .HasColumnType("TEXT");
+
+ b.Property("ImageOffset")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("Page")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.Property("XPath")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChapterId");
+
+ b.HasIndex("SeriesId");
+
+ b.HasIndex("VolumeId");
+
+ b.HasIndex("AppUserId", "SeriesId")
+ .HasDatabaseName("IX_AppUserBookmark_AppUserId_SeriesId");
+
+ b.ToTable("AppUserBookmark");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserCollection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRating")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0);
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastSyncUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("MissingSeriesFromSource")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("PrimaryColor")
+ .HasColumnType("TEXT");
+
+ b.Property("Promoted")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecondaryColor")
+ .HasColumnType("TEXT");
+
+ b.Property("Source")
+ .HasColumnType("INTEGER");
+
+ b.Property("SourceUrl")
+ .HasColumnType("TEXT");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.Property("TotalSourceCount")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserCollection");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserDashboardStream", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsProvided")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("SmartFilterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("StreamType")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(4);
+
+ b.Property("Visible")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SmartFilterId");
+
+ b.HasIndex("Visible");
+
+ b.ToTable("AppUserDashboardStream");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserExternalSource", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ApiKey")
+ .HasColumnType("TEXT");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Host")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserExternalSource");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserOnDeckRemoval");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRating", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("HasBeenRated")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("Rating")
+ .HasColumnType("REAL");
+
+ b.Property("Review")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserRating");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserReadingProfile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AllowAutomaticWebtoonReaderDetection")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("AutoCloseMenu")
+ .HasColumnType("INTEGER");
+
+ b.Property("BackgroundColor")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("#000000");
+
+ b.Property("BookReaderFontFamily")
+ .HasColumnType("TEXT");
+
+ b.Property("BookReaderFontSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderImmersiveMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLayoutMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLineSpacing")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderMargin")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderTapToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderWritingStyle")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0);
+
+ b.Property("BookThemeName")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("Dark");
+
+ b.PrimitiveCollection("DeviceIds")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("[]");
+
+ b.Property("DisableWidthOverride")
+ .HasColumnType("INTEGER");
+
+ b.Property("EmulateBook")
+ .HasColumnType("INTEGER");
+
+ b.Property("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property("LayoutMode")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("LibraryIds")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("[]");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("PageSplitOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("PdfScrollMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("PdfSpreadMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("PdfTheme")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReaderMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("ScalingOption")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("SeriesIds")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("[]");
+
+ b.Property("ShowScreenHints")
+ .HasColumnType("INTEGER");
+
+ b.Property("SwipeToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("WidthOverride")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserReadingProfiles");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("RoleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserSideNavStream", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExternalSourceId")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsProvided")
+ .HasColumnType("INTEGER");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("SmartFilterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("StreamType")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(5);
+
+ b.Property("Visible")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SmartFilterId");
+
+ b.HasIndex("Visible");
+
+ b.ToTable("AppUserSideNavStream");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserSmartFilter", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Filter")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserSmartFilter");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserTableOfContent", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookScrollId")
+ .HasColumnType("TEXT");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("PageNumber")
+ .HasColumnType("INTEGER");
+
+ b.Property("SelectedText")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("ChapterId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserTableOfContent");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserWantToRead", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserWantToRead");
+ });
+
+ modelBuilder.Entity("API.Entities.Chapter", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRatingLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("AlternateCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("AlternateNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("AlternateSeries")
+ .HasColumnType("TEXT");
+
+ b.Property("AverageExternalRating")
+ .HasColumnType("REAL");
+
+ b.Property("AvgHoursToRead")
+ .HasColumnType("REAL");
+
+ b.Property("CharacterLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ColoristLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Count")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverArtistLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("EditorLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("GenresLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ISBN")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("");
+
+ b.Property("ISBNLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ImprintLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("InkerLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsSpecial")
+ .HasColumnType("INTEGER");
+
+ b.Property("KPlusOverrides")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("[]");
+
+ b.Property("Language")
+ .HasColumnType("TEXT");
+
+ b.Property("LanguageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LettererLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("LocationLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxHoursToRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxNumber")
+ .HasColumnType("REAL");
+
+ b.Property("MinHoursToRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("MinNumber")
+ .HasColumnType("REAL");
+
+ b.Property("Number")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.Property("PencillerLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("PrimaryColor")
+ .HasColumnType("TEXT");
+
+ b.Property("PublisherLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Range")
+ .HasColumnType("TEXT");
+
+ b.Property("ReleaseDate")
+ .HasColumnType("TEXT");
+
+ b.Property("ReleaseDateLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecondaryColor")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesGroup")
+ .HasColumnType("TEXT");
+
+ b.Property("SortOrder")
+ .HasColumnType("REAL");
+
+ b.Property("SortOrderLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("StoryArc")
+ .HasColumnType("TEXT");
+
+ b.Property("StoryArcNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("SummaryLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("TagsLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property