Feature/bookmark feedback (#508)
* ImageService had a stream reset before writting out to array. Added logging statment for updating series metadata. Removed ConcurencyCheck due to bad update issue for CollectionTag. * Added a new screen which lets you quickly see all your bookmarks for a given user. * Built user bookmark page in user settings. Moved user settings to it's own lazy loaded module. Removed unneded debouncing from downloader and just used throttleTime instead. * Removed a not-yet implemented tab from series modal * Fixed a bug in clear bookmarks and adjusted icons within anchors to have proper styling
This commit is contained in:
parent
623e555633
commit
68bb5ed5a8
35 changed files with 554 additions and 270 deletions
|
@ -11,6 +11,7 @@ using API.Extensions;
|
|||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
|
@ -22,16 +23,18 @@ namespace API.Controllers
|
|||
private readonly IDirectoryService _directoryService;
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<ReaderController> _logger;
|
||||
private readonly ChapterSortComparer _chapterSortComparer = new ChapterSortComparer();
|
||||
private readonly ChapterSortComparerZeroFirst _chapterSortComparerForInChapterSorting = new ChapterSortComparerZeroFirst();
|
||||
private readonly NaturalSortComparer _naturalSortComparer = new NaturalSortComparer();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReaderController(IDirectoryService directoryService, ICacheService cacheService, IUnitOfWork unitOfWork)
|
||||
public ReaderController(IDirectoryService directoryService, ICacheService cacheService, IUnitOfWork unitOfWork, ILogger<ReaderController> logger)
|
||||
{
|
||||
_directoryService = directoryService;
|
||||
_cacheService = cacheService;
|
||||
_unitOfWork = unitOfWork;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -350,19 +353,31 @@ namespace API.Controllers
|
|||
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForChapter(user.Id, chapterId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all bookmarked pages for a User
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("get-all-bookmarks")]
|
||||
public async Task<ActionResult<IEnumerable<BookmarkDto>>> GetAllBookmarks()
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
if (user.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
|
||||
return Ok(await _unitOfWork.UserRepository.GetAllBookmarkDtos(user.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all bookmarks for all chapters linked to a Series
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("remove-bookmarks")]
|
||||
public async Task<ActionResult> RemoveBookmarks(int seriesId)
|
||||
public async Task<ActionResult> RemoveBookmarks(RemoveBookmarkForSeriesDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
if (user.Bookmarks == null) return Ok("Nothing to remove");
|
||||
try
|
||||
{
|
||||
user.Bookmarks = user.Bookmarks.Where(bmk => bmk.SeriesId == seriesId).ToList();
|
||||
user.Bookmarks = user.Bookmarks.Where(bmk => bmk.SeriesId != dto.SeriesId).ToList();
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
|
@ -370,8 +385,9 @@ namespace API.Controllers
|
|||
return Ok();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an exception when trying to clear bookmarks");
|
||||
await _unitOfWork.RollbackAsync();
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ using API.Interfaces;
|
|||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers
|
||||
|
@ -154,8 +155,8 @@ namespace API.Controllers
|
|||
}
|
||||
series.Name = updateSeries.Name.Trim();
|
||||
series.LocalizedName = updateSeries.LocalizedName.Trim();
|
||||
series.SortName = updateSeries.SortName.Trim();
|
||||
series.Summary = updateSeries.Summary.Trim();
|
||||
series.SortName = updateSeries.SortName?.Trim();
|
||||
series.Summary = updateSeries.Summary?.Trim(); // BUG: There was an exceptionSystem.NullReferenceException: Object reference not set to an instance of an object.
|
||||
|
||||
var needsRefreshMetadata = false;
|
||||
if (!updateSeries.CoverImageLocked)
|
||||
|
@ -296,8 +297,9 @@ namespace API.Controllers
|
|||
return Ok("Successfully updated");
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an exception when updating metadata");
|
||||
await _unitOfWork.RollbackAsync();
|
||||
}
|
||||
|
||||
|
@ -327,6 +329,19 @@ namespace API.Controllers
|
|||
return Ok(series);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches Series for a set of Ids. This will check User for permission access and filter out any Ids that don't exist or
|
||||
/// the user does not have access to.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("series-by-ids")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetAllSeriesById(SeriesByIdsDto dto)
|
||||
{
|
||||
if (dto.SeriesIds == null) return BadRequest("Must pass seriesIds");
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoForIdsAsync(dto.SeriesIds, user.Id));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
7
API/DTOs/RemoveBookmarkForSeriesDto.cs
Normal file
7
API/DTOs/RemoveBookmarkForSeriesDto.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace API.DTOs
|
||||
{
|
||||
public class RemoveBookmarkForSeriesDto
|
||||
{
|
||||
public int SeriesId { get; init; }
|
||||
}
|
||||
}
|
9
API/DTOs/SeriesByIdsDto.cs
Normal file
9
API/DTOs/SeriesByIdsDto.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class SeriesByIdsDto
|
||||
{
|
||||
public int[] SeriesIds { get; init; }
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@ namespace API.Data
|
|||
IsSpecial = specialTreatment,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static SeriesMetadata SeriesMetadata(ICollection<CollectionTag> collectionTags)
|
||||
{
|
||||
return new SeriesMetadata()
|
||||
|
@ -66,11 +66,11 @@ namespace API.Data
|
|||
return new CollectionTag()
|
||||
{
|
||||
Id = id,
|
||||
NormalizedTitle = API.Parser.Parser.Normalize(title).ToUpper(),
|
||||
Title = title,
|
||||
Summary = summary,
|
||||
NormalizedTitle = API.Parser.Parser.Normalize(title?.Trim()).ToUpper(),
|
||||
Title = title?.Trim(),
|
||||
Summary = summary?.Trim(),
|
||||
Promoted = promoted
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -443,5 +443,21 @@ namespace API.Data
|
|||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SeriesDto>> GetSeriesDtoForIdsAsync(IEnumerable<int> seriesIds, int userId)
|
||||
{
|
||||
var allowedLibraries = _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(library => library.AppUsers.Any(x => x.Id == userId))
|
||||
.Select(l => l.Id);
|
||||
|
||||
return await _context.Series
|
||||
.Where(s => seriesIds.Contains(s.Id) && allowedLibraries.Contains(s.LibraryId))
|
||||
.OrderBy(s => s.SortName)
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,17 @@ namespace API.Data
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BookmarkDto>> GetAllBookmarkDtos(int userId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(x => x.AppUserId == userId)
|
||||
.OrderBy(x => x.Page)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<MemberDto>> GetMembersAsync()
|
||||
{
|
||||
return await _context.Users
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace API.Entities
|
|||
/// Represents a user entered field that is used as a tagging and grouping mechanism
|
||||
/// </summary>
|
||||
[Index(nameof(Id), nameof(Promoted), IsUnique = true)]
|
||||
public class CollectionTag : IHasConcurrencyToken
|
||||
public class CollectionTag
|
||||
{
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
|
@ -42,12 +42,14 @@ namespace API.Entities
|
|||
|
||||
public ICollection<SeriesMetadata> SeriesMetadatas { get; set; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
[ConcurrencyCheck]
|
||||
/// <summary>
|
||||
/// Not Used due to not using concurrency update
|
||||
/// </summary>
|
||||
public uint RowVersion { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Not Used due to not using concurrency update
|
||||
/// </summary>
|
||||
public void OnSavingChanges()
|
||||
{
|
||||
RowVersion++;
|
||||
|
|
|
@ -63,5 +63,6 @@ namespace API.Interfaces
|
|||
Task<SeriesMetadataDto> GetSeriesMetadata(int seriesId);
|
||||
Task<PagedList<SeriesDto>> GetSeriesDtoForCollectionAsync(int collectionId, int userId, UserParams userParams);
|
||||
Task<IList<MangaFile>> GetFilesForSeries(int seriesId);
|
||||
Task<IEnumerable<SeriesDto>> GetSeriesDtoForIdsAsync(IEnumerable<int> seriesIds, int userId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,5 +19,6 @@ namespace API.Interfaces
|
|||
Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForSeries(int userId, int seriesId);
|
||||
Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForVolume(int userId, int volumeId);
|
||||
Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForChapter(int userId, int chapterId);
|
||||
Task<IEnumerable<BookmarkDto>> GetAllBookmarkDtos(int userId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ namespace API.Services
|
|||
using var img = Image.NewFromFile(path);
|
||||
using var stream = new MemoryStream();
|
||||
img.JpegsaveStream(stream);
|
||||
stream.Position = 0;
|
||||
return stream.ToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue