Misc Bugfixes and Enhancements (#507)

* Removed some extra spam for the console

* Implemented the code to update RowVersion, which is our concurrency check

* Fixed a critical issue where more than one bookmark could occur for a given chapter due to a race condition. Now we use concurrency checks and we also gracefully allow more than one row, by only grabbing first.

* Cleaned up the logic for IHasConcurencyToken and updated the setters to be private.

* Lots of comments and when deleting a library, remove any user progress items for which chapters don't exist.

* When deleting a Series, cleanup user progress rows.

* Now after a scan of library, if a series is removed, collection tags are pruned as well if there are no longer any series bound to it.

* Updated the image on the Readme to show a better picture

* Small code cleanup to remove null check modifier as I check for null just before then

* Fixed images loading multiple times due to using function in binding with random. You can now click chapter images to read that chapter specifically.

* Fixed cards being different sizes when read vs unread

* Moved over Robbie's workflow changes from notifier. Commented out activity indicators as that is not shipping with this release.

* Remove code that isn't needed

* Reverted GA

* Changed GA to trigger only when HEAD is updated
This commit is contained in:
Joseph Milazzo 2021-08-17 14:15:32 -07:00 committed by GitHub
parent 16faa82d20
commit 3ed99afd32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1378 additions and 81 deletions

View file

@ -18,6 +18,9 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers
{
/// <summary>
/// All Account matters
/// </summary>
public class AccountController : BaseApiController
{
private readonly UserManager<AppUser> _userManager;
@ -27,9 +30,10 @@ namespace API.Controllers
private readonly ILogger<AccountController> _logger;
private readonly IMapper _mapper;
/// <inheritdoc />
public AccountController(UserManager<AppUser> userManager,
SignInManager<AppUser> signInManager,
ITokenService tokenService, IUnitOfWork unitOfWork,
SignInManager<AppUser> signInManager,
ITokenService tokenService, IUnitOfWork unitOfWork,
ILogger<AccountController> logger,
IMapper mapper)
{
@ -40,7 +44,12 @@ namespace API.Controllers
_logger = logger;
_mapper = mapper;
}
/// <summary>
/// Update a user's password
/// </summary>
/// <param name="resetPasswordDto"></param>
/// <returns></returns>
[HttpPost("reset-password")]
public async Task<ActionResult> UpdatePassword(ResetPasswordDto resetPasswordDto)
{
@ -49,7 +58,7 @@ namespace API.Controllers
if (resetPasswordDto.UserName != User.GetUsername() && !User.IsInRole(PolicyConstants.AdminRole))
return Unauthorized("You are not permitted to this operation.");
// Validate Password
foreach (var validator in _userManager.PasswordValidators)
{
@ -60,26 +69,31 @@ namespace API.Controllers
validationResult.Errors.Select(e => new ApiException(400, e.Code, e.Description)));
}
}
var result = await _userManager.RemovePasswordAsync(user);
if (!result.Succeeded)
{
_logger.LogError("Could not update password");
return BadRequest(result.Errors.Select(e => new ApiException(400, e.Code, e.Description)));
}
result = await _userManager.AddPasswordAsync(user, resetPasswordDto.Password);
if (!result.Succeeded)
{
_logger.LogError("Could not update password");
return BadRequest(result.Errors.Select(e => new ApiException(400, e.Code, e.Description)));
}
_logger.LogInformation("{User}'s Password has been reset", resetPasswordDto.UserName);
return Ok();
}
/// <summary>
/// Register a new user on the server
/// </summary>
/// <param name="registerDto"></param>
/// <returns></returns>
[HttpPost("register")]
public async Task<ActionResult<UserDto>> Register(RegisterDto registerDto)
{
@ -134,6 +148,11 @@ namespace API.Controllers
return BadRequest("Something went wrong when registering user");
}
/// <summary>
/// Perform a login. Will send JWT Token of the logged in user back.
/// </summary>
/// <param name="loginDto"></param>
/// <returns></returns>
[HttpPost("login")]
public async Task<ActionResult<UserDto>> Login(LoginDto loginDto)
{
@ -147,14 +166,14 @@ namespace API.Controllers
.CheckPasswordSignInAsync(user, loginDto.Password, false);
if (!result.Succeeded) return Unauthorized("Your credentials are not correct.");
// Update LastActive on account
user.LastActive = DateTime.Now;
user.UserPreferences ??= new AppUserPreferences();
_unitOfWork.UserRepository.Update(user);
await _unitOfWork.CommitAsync();
_logger.LogInformation("{UserName} logged in at {Time}", user.UserName, user.LastActive);
return new UserDto
@ -165,6 +184,10 @@ namespace API.Controllers
};
}
/// <summary>
/// Get All Roles back. See <see cref="PolicyConstants"/>
/// </summary>
/// <returns></returns>
[HttpGet("roles")]
public ActionResult<IList<string>> GetRoles()
{
@ -175,6 +198,11 @@ namespace API.Controllers
f => (string) f.GetValue(null)).Values.ToList();
}
/// <summary>
/// Sets the given roles to the user.
/// </summary>
/// <param name="updateRbsDto"></param>
/// <returns></returns>
[HttpPost("update-rbs")]
public async Task<ActionResult> UpdateRoles(UpdateRbsDto updateRbsDto)
{
@ -190,7 +218,7 @@ namespace API.Controllers
var existingRoles = (await _userManager.GetRolesAsync(user))
.Where(s => s != PolicyConstants.AdminRole && s != PolicyConstants.PlebRole)
.ToList();
// Find what needs to be added and what needs to be removed
var rolesToRemove = existingRoles.Except(updateRbsDto.Roles);
var result = await _userManager.AddToRolesAsync(user, updateRbsDto.Roles);
@ -204,10 +232,10 @@ namespace API.Controllers
{
return Ok();
}
await _unitOfWork.RollbackAsync();
return BadRequest("Something went wrong, unable to update user's roles");
}
}
}
}

View file

@ -185,6 +185,8 @@ namespace API.Controllers
if (chapterIds.Any())
{
await _unitOfWork.AppUserProgressRepository.CleanupAbandonedChapters();
await _unitOfWork.CommitAsync();
_taskScheduler.CleanupChapters(chapterIds);
}
return Ok(true);

View file

@ -14,6 +14,9 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
/// <summary>
/// For all things regarding reading, mainly focusing on non-Book related entities
/// </summary>
public class ReaderController : BaseApiController
{
private readonly IDirectoryService _directoryService;
@ -23,6 +26,7 @@ namespace API.Controllers
private readonly ChapterSortComparerZeroFirst _chapterSortComparerForInChapterSorting = new ChapterSortComparerZeroFirst();
private readonly NaturalSortComparer _naturalSortComparer = new NaturalSortComparer();
/// <inheritdoc />
public ReaderController(IDirectoryService directoryService, ICacheService cacheService, IUnitOfWork unitOfWork)
{
_directoryService = directoryService;
@ -30,6 +34,12 @@ namespace API.Controllers
_unitOfWork = unitOfWork;
}
/// <summary>
/// Returns an image for a given chapter. Side effect: This will cache the chapter images for reading.
/// </summary>
/// <param name="chapterId"></param>
/// <param name="page"></param>
/// <returns></returns>
[HttpGet("image")]
public async Task<ActionResult> GetImage(int chapterId, int page)
{
@ -57,6 +67,12 @@ namespace API.Controllers
}
}
/// <summary>
/// Returns various information about a Chapter. Side effect: This will cache the chapter images for reading.
/// </summary>
/// <param name="seriesId"></param>
/// <param name="chapterId"></param>
/// <returns></returns>
[HttpGet("chapter-info")]
public async Task<ActionResult<ChapterInfoDto>> GetChapterInfo(int seriesId, int chapterId)
{
@ -149,6 +165,11 @@ namespace API.Controllers
return userProgress;
}
/// <summary>
/// Marks a Chapter as Unread (progress)
/// </summary>
/// <param name="markReadDto"></param>
/// <returns></returns>
[HttpPost("mark-unread")]
public async Task<ActionResult> MarkUnread(MarkReadDto markReadDto)
{
@ -179,6 +200,11 @@ namespace API.Controllers
return BadRequest("There was an issue saving progress");
}
/// <summary>
/// Marks all chapters within a volume as Read
/// </summary>
/// <param name="markVolumeReadDto"></param>
/// <returns></returns>
[HttpPost("mark-volume-read")]
public async Task<ActionResult> MarkVolumeAsRead(MarkVolumeReadDto markVolumeReadDto)
{
@ -218,6 +244,11 @@ namespace API.Controllers
return BadRequest("Could not save progress");
}
/// <summary>
/// Returns Progress (page number) for a chapter for the logged in user
/// </summary>
/// <param name="chapterId"></param>
/// <returns></returns>
[HttpGet("get-progress")]
public async Task<ActionResult<ProgressDto>> GetProgress(int chapterId)
{
@ -242,6 +273,11 @@ namespace API.Controllers
return Ok(progressBookmark);
}
/// <summary>
/// Save page against Chapter for logged in user
/// </summary>
/// <param name="progressDto"></param>
/// <returns></returns>
[HttpPost("progress")]
public async Task<ActionResult> BookmarkProgress(ProgressDto progressDto)
{
@ -259,13 +295,11 @@ namespace API.Controllers
progressDto.PageNum = 0;
}
try
{
// TODO: Look into creating a progress entry when a new item is added to the DB so we can just look it up and modify it
user.Progresses ??= new List<AppUserProgress>();
user.Progresses ??= new List<AppUserProgress>();
var userProgress =
user.Progresses.SingleOrDefault(x => x.ChapterId == progressDto.ChapterId && x.AppUserId == user.Id);
user.Progresses.FirstOrDefault(x => x.ChapterId == progressDto.ChapterId && x.AppUserId == user.Id);
if (userProgress == null)
{
@ -303,6 +337,11 @@ namespace API.Controllers
return BadRequest("Could not save progress");
}
/// <summary>
/// Returns a list of bookmarked pages for a given Chapter
/// </summary>
/// <param name="chapterId"></param>
/// <returns></returns>
[HttpGet("get-bookmarks")]
public async Task<ActionResult<IEnumerable<BookmarkDto>>> GetBookmarks(int chapterId)
{
@ -311,6 +350,11 @@ namespace API.Controllers
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForChapter(user.Id, chapterId));
}
/// <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)
{
@ -335,6 +379,11 @@ namespace API.Controllers
}
/// <summary>
/// Returns all bookmarked pages for a given volume
/// </summary>
/// <param name="volumeId"></param>
/// <returns></returns>
[HttpGet("get-volume-bookmarks")]
public async Task<ActionResult<IEnumerable<BookmarkDto>>> GetBookmarksForVolume(int volumeId)
{
@ -343,6 +392,11 @@ namespace API.Controllers
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForVolume(user.Id, volumeId));
}
/// <summary>
/// Returns all bookmarked pages for a given series
/// </summary>
/// <param name="seriesId"></param>
/// <returns></returns>
[HttpGet("get-series-bookmarks")]
public async Task<ActionResult<IEnumerable<BookmarkDto>>> GetBookmarksForSeries(int seriesId)
{
@ -352,6 +406,11 @@ namespace API.Controllers
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForSeries(user.Id, seriesId));
}
/// <summary>
/// Bookmarks a page against a Chapter
/// </summary>
/// <param name="bookmarkDto"></param>
/// <returns></returns>
[HttpPost("bookmark")]
public async Task<ActionResult> BookmarkPage(BookmarkDto bookmarkDto)
{
@ -408,6 +467,11 @@ namespace API.Controllers
return BadRequest("Could not save bookmark");
}
/// <summary>
/// Removes a bookmarked page for a Chapter
/// </summary>
/// <param name="bookmarkDto"></param>
/// <returns></returns>
[HttpPost("unbookmark")]
public async Task<ActionResult> UnBookmarkPage(BookmarkDto bookmarkDto)
{

View file

@ -79,6 +79,9 @@ namespace API.Controllers
if (result)
{
await _unitOfWork.AppUserProgressRepository.CleanupAbandonedChapters();
await _unitOfWork.CollectionTagRepository.RemoveTagsWithoutSeries();
await _unitOfWork.CommitAsync();
_taskScheduler.CleanupChapters(chapterIds);
}
return Ok(result);