mirror of
https://github.com/Kareadita/Kavita.git
synced 2026-06-06 14:55:19 -04:00
OPDS Enhancements, Epub fixes, and a lot more (#4035)
Co-authored-by: Amelia <77553571+Fesaa@users.noreply.github.com> Co-authored-by: Robbie Davis <robbie@therobbiedavis.com> Co-authored-by: Fabian Pammer <fpammer@mantro.net> Co-authored-by: Vinícius Licz <vinilicz@gmail.com>
This commit is contained in:
@@ -139,6 +139,9 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
||||
builder.Entity<AppUserPreferences>()
|
||||
.Property(b => b.AllowAutomaticWebtoonReaderDetection)
|
||||
.HasDefaultValue(true);
|
||||
builder.Entity<AppUserPreferences>()
|
||||
.Property(b => b.ColorScapeEnabled)
|
||||
.HasDefaultValue(true);
|
||||
|
||||
builder.Entity<Library>()
|
||||
.Property(b => b.AllowScrobbling)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ColorScapeSetting : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "ColorScapeEnabled",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ColorScapeEnabled",
|
||||
table: "AppUserPreferences");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -551,6 +551,11 @@ namespace API.Data.Migrations
|
||||
b.Property<bool>("CollapseSeriesRelationships")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ColorScapeEnabled")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("EmulateBook")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs.Dashboard;
|
||||
using API.Entities;
|
||||
using API.Helpers;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -16,6 +17,7 @@ public interface IAppUserSmartFilterRepository
|
||||
void Attach(AppUserSmartFilter filter);
|
||||
void Delete(AppUserSmartFilter filter);
|
||||
IEnumerable<SmartFilterDto> GetAllDtosByUserId(int userId);
|
||||
Task<PagedList<SmartFilterDto>> GetPagedDtosByUserIdAsync(int userId, UserParams userParams);
|
||||
Task<AppUserSmartFilter?> GetById(int smartFilterId);
|
||||
|
||||
}
|
||||
@@ -54,6 +56,15 @@ public class AppUserSmartFilterRepository : IAppUserSmartFilterRepository
|
||||
.AsEnumerable();
|
||||
}
|
||||
|
||||
public Task<PagedList<SmartFilterDto>> GetPagedDtosByUserIdAsync(int userId, UserParams userParams)
|
||||
{
|
||||
var filters = _context.AppUserSmartFilter
|
||||
.Where(f => f.AppUserId == userId)
|
||||
.ProjectTo<SmartFilterDto>(_mapper.ConfigurationProvider);
|
||||
|
||||
return PagedList<SmartFilterDto>.CreateAsync(filters, userParams);
|
||||
}
|
||||
|
||||
public async Task<AppUserSmartFilter?> GetById(int smartFilterId)
|
||||
{
|
||||
return await _context.AppUserSmartFilter
|
||||
|
||||
@@ -40,10 +40,12 @@ public interface IChapterRepository
|
||||
Task<IChapterInfoDto?> GetChapterInfoDtoAsync(int chapterId);
|
||||
Task<int> GetChapterTotalPagesAsync(int chapterId);
|
||||
Task<Chapter?> GetChapterAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files);
|
||||
Task<ChapterDto?> GetChapterDtoAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files);
|
||||
Task<ChapterDto?> GetChapterDtoAsync(int chapterId, int userId);
|
||||
Task<IList<ChapterDto>> GetChapterDtoByIdsAsync(IEnumerable<int> chapterIds, int userId);
|
||||
Task<ChapterMetadataDto?> GetChapterMetadataDtoAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files);
|
||||
Task<IList<MangaFile>> GetFilesForChapterAsync(int chapterId);
|
||||
Task<IList<Chapter>> GetChaptersAsync(int volumeId, ChapterIncludes includes = ChapterIncludes.None);
|
||||
Task<IList<ChapterDto>> GetChapterDtosAsync(int volumeId, int userId);
|
||||
Task<IList<MangaFile>> GetFilesForChaptersAsync(IReadOnlyList<int> chapterIds);
|
||||
Task<string?> GetChapterCoverImageAsync(int chapterId);
|
||||
Task<IList<string>> GetAllCoverImagesAsync();
|
||||
@@ -153,18 +155,39 @@ public class ChapterRepository : IChapterRepository
|
||||
.Select(c => c.Pages)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
public async Task<ChapterDto?> GetChapterDtoAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files)
|
||||
public async Task<ChapterDto?> GetChapterDtoAsync(int chapterId, int userId)
|
||||
{
|
||||
var chapter = await _context.Chapter
|
||||
.Includes(includes)
|
||||
.Includes(ChapterIncludes.Files | ChapterIncludes.People)
|
||||
.ProjectTo<ChapterDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
.FirstOrDefaultAsync(c => c.Id == chapterId);
|
||||
|
||||
if (userId > 0 && chapter != null)
|
||||
{
|
||||
await AddChapterModifiers(userId, chapter);
|
||||
}
|
||||
|
||||
return chapter;
|
||||
}
|
||||
|
||||
public async Task<IList<ChapterDto>> GetChapterDtoByIdsAsync(IEnumerable<int> chapterIds, int userId)
|
||||
{
|
||||
var chapters = await _context.Chapter
|
||||
.Where(c => chapterIds.Contains(c.Id))
|
||||
.Includes(ChapterIncludes.Files | ChapterIncludes.People)
|
||||
.ProjectTo<ChapterDto>(_mapper.ConfigurationProvider)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync() ;
|
||||
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
await AddChapterModifiers(userId, chapter);
|
||||
}
|
||||
|
||||
return chapters;
|
||||
}
|
||||
|
||||
public async Task<ChapterMetadataDto?> GetChapterMetadataDtoAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files)
|
||||
{
|
||||
var chapter = await _context.Chapter
|
||||
@@ -218,6 +241,28 @@ public class ChapterRepository : IChapterRepository
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Chapters for a volume id with Progress
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<ChapterDto>> GetChapterDtosAsync(int volumeId, int userId)
|
||||
{
|
||||
var chapts = await _context.Chapter
|
||||
.Where(c => c.VolumeId == volumeId)
|
||||
.Includes(ChapterIncludes.Files | ChapterIncludes.People)
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.ProjectTo<ChapterDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var chapter in chapts)
|
||||
{
|
||||
await AddChapterModifiers(userId, chapter);
|
||||
}
|
||||
|
||||
return chapts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cover image for a chapter id.
|
||||
/// </summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Extensions.QueryExtensions;
|
||||
using API.Extensions.QueryExtensions.Filtering;
|
||||
using API.Helpers;
|
||||
using API.Services.Plus;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
@@ -49,6 +50,7 @@ public interface ICollectionTagRepository
|
||||
/// <param name="includePromoted"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<AppUserCollectionDto>> GetCollectionDtosAsync(int userId, bool includePromoted = false);
|
||||
Task<PagedList<AppUserCollectionDto>> GetCollectionDtosPagedAsync(int userId, UserParams userParams, bool includePromoted = false);
|
||||
Task<IEnumerable<AppUserCollectionDto>> GetCollectionDtosBySeriesAsync(int userId, int seriesId, bool includePromoted = false);
|
||||
|
||||
Task<IList<string>> GetAllCoverImagesAsync();
|
||||
@@ -117,6 +119,18 @@ public class CollectionTagRepository : ICollectionTagRepository
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<PagedList<AppUserCollectionDto>> GetCollectionDtosPagedAsync(int userId, UserParams userParams, bool includePromoted = false)
|
||||
{
|
||||
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||
var collections = _context.AppUserCollection
|
||||
.Where(uc => uc.AppUserId == userId || (includePromoted && uc.Promoted))
|
||||
.WhereIf(ageRating.AgeRating != AgeRating.NotApplicable, uc => uc.AgeRating <= ageRating.AgeRating)
|
||||
.OrderBy(uc => uc.Title)
|
||||
.ProjectTo<AppUserCollectionDto>(_mapper.ConfigurationProvider);
|
||||
|
||||
return await PagedList<AppUserCollectionDto>.CreateAsync(collections, userParams);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AppUserCollectionDto>> GetCollectionDtosBySeriesAsync(int userId, int seriesId, bool includePromoted = false)
|
||||
{
|
||||
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||
|
||||
@@ -26,8 +26,8 @@ public interface IGenreRepository
|
||||
Task RemoveAllGenreNoLongerAssociated(bool removeExternal = false);
|
||||
Task<IList<GenreTagDto>> GetAllGenreDtosForLibrariesAsync(int userId, IList<int>? libraryIds = null, QueryContext context = QueryContext.None);
|
||||
Task<int> GetCountAsync();
|
||||
Task<GenreTagDto> GetRandomGenre();
|
||||
Task<GenreTagDto> GetGenreById(int id);
|
||||
Task<GenreTagDto?> GetRandomGenre();
|
||||
Task<GenreTagDto?> GetGenreById(int id);
|
||||
Task<List<string>> GetAllGenresNotInListAsync(ICollection<string> genreNames);
|
||||
Task<PagedList<BrowseGenreDto>> GetBrowseableGenre(int userId, UserParams userParams);
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public class GenreRepository : IGenreRepository
|
||||
return await _context.Genre.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<GenreTagDto> GetRandomGenre()
|
||||
public async Task<GenreTagDto?> GetRandomGenre()
|
||||
{
|
||||
var genreCount = await GetCountAsync();
|
||||
if (genreCount == 0) return null;
|
||||
@@ -92,7 +92,7 @@ public class GenreRepository : IGenreRepository
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<GenreTagDto> GetGenreById(int id)
|
||||
public async Task<GenreTagDto?> GetGenreById(int id)
|
||||
{
|
||||
return await _context.Genre
|
||||
.Where(g => g.Id == id)
|
||||
|
||||
@@ -31,7 +31,7 @@ public interface IReadingListRepository
|
||||
{
|
||||
Task<PagedList<ReadingListDto>> GetReadingListDtosForUserAsync(int userId, bool includePromoted, UserParams userParams, bool sortByLastModified = true);
|
||||
Task<ReadingList?> GetReadingListByIdAsync(int readingListId, ReadingListIncludes includes = ReadingListIncludes.None);
|
||||
Task<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int readingListId, int userId);
|
||||
Task<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int readingListId, int userId, UserParams? userParams = null);
|
||||
Task<ReadingListDto?> GetReadingListDtoByIdAsync(int readingListId, int userId);
|
||||
Task<IEnumerable<ReadingListItemDto>> AddReadingProgressModifiers(int userId, IList<ReadingListItemDto> items);
|
||||
Task<ReadingListDto?> GetReadingListDtoByTitleAsync(int userId, string title);
|
||||
@@ -357,11 +357,11 @@ public class ReadingListRepository : IReadingListRepository
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int readingListId, int userId)
|
||||
public async Task<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int readingListId, int userId, UserParams? userParams = null)
|
||||
{
|
||||
var userLibraries = _context.Library.GetUserLibraries(userId);
|
||||
|
||||
var items = await _context.ReadingListItem
|
||||
var query = _context.ReadingListItem
|
||||
.Where(s => s.ReadingListId == readingListId)
|
||||
.Join(_context.Chapter, s => s.ChapterId, chapter => chapter.Id, (data, chapter) => new
|
||||
{
|
||||
@@ -431,9 +431,17 @@ public class ReadingListRepository : IReadingListRepository
|
||||
})
|
||||
.Where(o => userLibraries.Contains(o.LibraryId))
|
||||
.OrderBy(rli => rli.Order)
|
||||
.AsSplitQuery()
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
.AsSplitQuery();
|
||||
|
||||
if (userParams != null)
|
||||
{
|
||||
query = query
|
||||
.Skip(userParams.PageNumber * userParams.PageSize)
|
||||
.Take(userParams.PageSize);
|
||||
}
|
||||
|
||||
|
||||
var items = await query.ToListAsync();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
|
||||
@@ -29,20 +29,20 @@ namespace API.Data.Repositories;
|
||||
public enum AppUserIncludes
|
||||
{
|
||||
None = 1,
|
||||
Progress = 2,
|
||||
Bookmarks = 4,
|
||||
ReadingLists = 8,
|
||||
Ratings = 16,
|
||||
UserPreferences = 32,
|
||||
WantToRead = 64,
|
||||
ReadingListsWithItems = 128,
|
||||
Devices = 256,
|
||||
ScrobbleHolds = 512,
|
||||
SmartFilters = 1024,
|
||||
DashboardStreams = 2048,
|
||||
SideNavStreams = 4096,
|
||||
ExternalSources = 8192,
|
||||
Collections = 16384, // 2^14
|
||||
Progress = 1 << 1,
|
||||
Bookmarks = 1 << 2,
|
||||
ReadingLists = 1 << 3,
|
||||
Ratings = 1 << 4,
|
||||
UserPreferences = 1 << 5,
|
||||
WantToRead = 1 << 6,
|
||||
ReadingListsWithItems = 1 << 7,
|
||||
Devices = 1 << 8,
|
||||
ScrobbleHolds = 1 << 9,
|
||||
SmartFilters = 1 << 10,
|
||||
DashboardStreams = 1 << 11,
|
||||
SideNavStreams = 1 << 12,
|
||||
ExternalSources = 1 << 13,
|
||||
Collections = 1 << 14,
|
||||
ChapterRatings = 1 << 15,
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ public interface IUserRepository
|
||||
Task<AppUser?> GetByOidcId(string? oidcId, AppUserIncludes includes = AppUserIncludes.None);
|
||||
|
||||
Task<AnnotationDto?> GetAnnotationDtoById(int userId, int annotationId);
|
||||
Task<List<AnnotationDto>> GetAnnotationDtosBySeries(int userId, int seriesId);
|
||||
}
|
||||
|
||||
public class UserRepository : IUserRepository
|
||||
@@ -612,6 +613,14 @@ public class UserRepository : IUserRepository
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<List<AnnotationDto>> GetAnnotationDtosBySeries(int userId, int seriesId)
|
||||
{
|
||||
return await _context.AppUserAnnotation
|
||||
.Where(a => a.AppUserId == userId && a.SeriesId == seriesId)
|
||||
.ProjectTo<AnnotationDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<AppUser>> GetAdminUsersAsync()
|
||||
{
|
||||
@@ -629,6 +638,7 @@ public class UserRepository : IUserRepository
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
if (user == null) return ArraySegment<string>.Empty;
|
||||
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (_userManager == null)
|
||||
{
|
||||
// userManager is null on Unit Tests only
|
||||
|
||||
Reference in New Issue
Block a user