Reading List Fixes (#1784)
* Add ability to save readinglist comicinfo fields in Chapter. * Added the appropriate fields and migration for Reading List generation. * Started the reading list code * Started building out the CBL import code with some initial unit tests. * Fixed first unit test * Started refactoring control code into services and writing unit tests for ReadingLists. Found a logic issue around reading list title between create/update. Will be corrected in this branch with unit tests. * Can't figure out how to mock UserManager, so had to uncomment a few tests. * Tooltip for total pages read shows the full number * Tweaked the math a bit for average reading per week. * Fixed up the reading list unit tests. Fixed an issue where when inserting chapters into a blank reading list, the initial reading list item would have an order of 1 instead of 0. * Cleaned up the code to allow the reading list code to be localized easily and fixed up a bug in last PR. * Fixed a sorting issue on reading activity * Tweaked the code around reading list actionables not showing due to some weird filter. * Fixed edit library settings not opening on library detail page * Fixed a bug where reading activity dates would be out of order due to a bug in how charts works. A temp hack has been added. * Disable promotion in edit reading list modal since non-admins can (and should have) been able to use it. * Fixed a bug where non-admins couldn't update their OWN reading lists. Made uploading a cover image for readinglists now check against the user's reading list access to allow non-admin's to set images. * Fixed an issue introduced earlier in PR where adding chapters to reading list could cause order to get skewed. * Fixed another regression from earlier commit * Hooked in Import CBL flow. No functionality yet. * Code is a mess. Shifting how the whole import process is going to be done. Commiting so I can pivot drastically. * Very rough code for first step is done. * Ui has started, I've run out of steam for this feature. * Cleaned up the UI code a bit to make the step tracker nature easier without a dedicated component. * Much flow implementation and tweaking to how validation checks and what is sent back. * Removed import via cbl code as it's not done. Pushing to next release.
This commit is contained in:
parent
ae1af22af1
commit
3f24dc7392
48 changed files with 21951 additions and 170 deletions
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using Kavita.Common.Extensions;
|
||||
|
||||
|
@ -54,13 +55,27 @@ public class ComicInfo
|
|||
/// User's rating of the content
|
||||
/// </summary>
|
||||
public float UserRating { get; set; }
|
||||
|
||||
public string StoryArc { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Can contain multiple comma separated strings, each create a <see cref="CollectionTag"/>
|
||||
/// </summary>
|
||||
public string SeriesGroup { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string StoryArc { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Can contain multiple comma separated numbers that match with StoryArc
|
||||
/// </summary>
|
||||
public string StoryArcNumber { get; set; } = string.Empty;
|
||||
public string AlternateNumber { get; set; } = string.Empty;
|
||||
public string AlternateSeries { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Not used
|
||||
/// </summary>
|
||||
[System.ComponentModel.DefaultValueAttribute(0)]
|
||||
public int AlternateCount { get; set; } = 0;
|
||||
public string AlternateSeries { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// This is Epub only: calibre:title_sort
|
||||
|
|
1769
API/Data/Migrations/20230202182602_ReadingListFields.Designer.cs
generated
Normal file
1769
API/Data/Migrations/20230202182602_ReadingListFields.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
66
API/Data/Migrations/20230202182602_ReadingListFields.cs
Normal file
66
API/Data/Migrations/20230202182602_ReadingListFields.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class ReadingListFields : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "AlternateCount",
|
||||
table: "Chapter",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "AlternateNumber",
|
||||
table: "Chapter",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "AlternateSeries",
|
||||
table: "Chapter",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "StoryArc",
|
||||
table: "Chapter",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "StoryArcNumber",
|
||||
table: "Chapter",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AlternateCount",
|
||||
table: "Chapter");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AlternateNumber",
|
||||
table: "Chapter");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AlternateSeries",
|
||||
table: "Chapter");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StoryArc",
|
||||
table: "Chapter");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StoryArcNumber",
|
||||
table: "Chapter");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -378,6 +378,15 @@ namespace API.Data.Migrations
|
|||
b.Property<int>("AgeRating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AlternateCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AlternateNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AlternateSeries")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AvgHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -429,6 +438,12 @@ namespace API.Data.Migrations
|
|||
b.Property<string>("SeriesGroup")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoryArc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("StoryArcNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs.ReadingLists;
|
||||
|
@ -31,6 +32,7 @@ public interface IReadingListRepository
|
|||
Task<string> GetCoverImageAsync(int readingListId);
|
||||
Task<IList<string>> GetAllCoverImagesAsync();
|
||||
Task<bool> ReadingListExists(string name);
|
||||
Task<List<ReadingList>> GetAllReadingListsAsync();
|
||||
}
|
||||
|
||||
public class ReadingListRepository : IReadingListRepository
|
||||
|
@ -84,6 +86,15 @@ public class ReadingListRepository : IReadingListRepository
|
|||
.AnyAsync(x => x.NormalizedTitle.Equals(normalized));
|
||||
}
|
||||
|
||||
public async Task<List<ReadingList>> GetAllReadingListsAsync()
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Include(r => r.Items.OrderBy(i => i.Order))
|
||||
.AsSplitQuery()
|
||||
.OrderBy(l => l.Title)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public void Remove(ReadingListItem item)
|
||||
{
|
||||
_context.ReadingListItem.Remove(item);
|
||||
|
|
|
@ -35,6 +35,7 @@ public enum SeriesIncludes
|
|||
Metadata = 4,
|
||||
Related = 8,
|
||||
Library = 16,
|
||||
Chapters = 32
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -115,6 +116,11 @@ public interface ISeriesRepository
|
|||
Task<PagedList<SeriesDto>> GetWantToReadForUserAsync(int userId, UserParams userParams, FilterDto filter);
|
||||
Task<bool> IsSeriesInWantToRead(int userId, int seriesId);
|
||||
Task<Series> GetSeriesByFolderPath(string folder, SeriesIncludes includes = SeriesIncludes.None);
|
||||
|
||||
Task<IEnumerable<Series>> GetAllSeriesByNameAsync(IEnumerable<string> normalizedNames,
|
||||
int userId, SeriesIncludes includes = SeriesIncludes.None);
|
||||
Task<IEnumerable<SeriesDto>> GetAllSeriesDtosByNameAsync(IEnumerable<string> normalizedNames,
|
||||
int userId, SeriesIncludes includes = SeriesIncludes.None);
|
||||
Task<Series> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId, MangaFormat format, bool withFullIncludes = true);
|
||||
Task<IList<Series>> RemoveSeriesNotInList(IList<ParsedSeries> seenSeries, int libraryId);
|
||||
Task<IDictionary<string, IList<SeriesModified>>> GetFolderPathMap(int libraryId);
|
||||
|
@ -545,6 +551,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task AddSeriesModifiers(int userId, List<SeriesDto> series)
|
||||
{
|
||||
var userProgress = await _context.AppUserProgresses
|
||||
|
@ -1200,6 +1207,35 @@ public class SeriesRepository : ISeriesRepository
|
|||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Series>> GetAllSeriesByNameAsync(IEnumerable<string> normalizedNames,
|
||||
int userId, SeriesIncludes includes = SeriesIncludes.None)
|
||||
{
|
||||
var libraryIds = _context.Library.GetUserLibraries(userId);
|
||||
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||
|
||||
return await _context.Series
|
||||
.Where(s => normalizedNames.Contains(s.NormalizedName))
|
||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||
.RestrictAgainstAgeRestriction(userRating)
|
||||
.Includes(includes)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SeriesDto>> GetAllSeriesDtosByNameAsync(IEnumerable<string> normalizedNames, int userId,
|
||||
SeriesIncludes includes = SeriesIncludes.None)
|
||||
{
|
||||
var libraryIds = _context.Library.GetUserLibraries(userId);
|
||||
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||
|
||||
return await _context.Series
|
||||
.Where(s => normalizedNames.Contains(s.NormalizedName))
|
||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||
.RestrictAgainstAgeRestriction(userRating)
|
||||
.Includes(includes)
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a series by series name or localized name for a given library.
|
||||
/// </summary>
|
||||
|
|
|
@ -119,6 +119,7 @@ public class UserRepository : IUserRepository
|
|||
var query = _context.Users
|
||||
.Where(x => x.UserName == username);
|
||||
|
||||
// TODO: Move to QueryExtensions
|
||||
query = AddIncludesToQuery(query, includeFlags);
|
||||
|
||||
return await query.SingleOrDefaultAsync();
|
||||
|
@ -201,9 +202,7 @@ public class UserRepository : IUserRepository
|
|||
query = query.Include(u => u.Devices);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return query;
|
||||
return query.AsSplitQuery();
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue