Swagger, Tachiyomi, and some new settings (#1331)
* Fixed up swagger generation * Updated Tachiyomi's latest-chapter to hopefully solve some sync issues. * Fixed #1279 with table of contents due to new EPubReader * When errors occur, show the event widget icon in red * Lots of documentation added and tweaked some wording around backups and swagger * For promidius * Return proper ChapterDTO * Hacks for Promidius * Cleanup code * No loose leaf, send max chapter * One more encode change * Implemented code per promiduius' requirements * Fixed a bug in the epub parsing where even if you had a series index and series group, but didn't have the series in the title, Kavita wouldn't group them properly. * Removed some extra comment * Implemented the ability to change a library's type after it's been setup. This displays a warning explaining the dangers of it. * Removed some whitespace * Blur descriptions based on read status for list item view to avoid spoilers * Tweaked placement of a tooltip due to new series detail styles * Hooked up a user preference for bluring unread summaries. Fixed a bug in refresh token where we would cause re-authentication when it shouldn't be needed.
This commit is contained in:
parent
f2c08092b8
commit
2ab0aedd22
36 changed files with 1851 additions and 72 deletions
|
@ -207,6 +207,11 @@ namespace API.Controllers
|
|||
return dto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the user's JWT token
|
||||
/// </summary>
|
||||
/// <param name="tokenRequestDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("refresh-token")]
|
||||
public async Task<ActionResult<TokenRequestDto>> RefreshToken([FromBody] TokenRequestDto tokenRequestDto)
|
||||
{
|
||||
|
|
|
@ -14,6 +14,10 @@ namespace API.Controllers
|
|||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an admin exists on the system. This is essentially a check to validate if the system has been setup.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("exists")]
|
||||
public async Task<ActionResult<bool>> AdminExists()
|
||||
{
|
||||
|
@ -21,4 +25,4 @@ namespace API.Controllers
|
|||
return users.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,12 @@ namespace API.Controllers
|
|||
_cacheService = cacheService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves information for the PDF and Epub reader
|
||||
/// </summary>
|
||||
/// <remarks>This only applies to Epub or PDF files</remarks>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{chapterId}/book-info")]
|
||||
public async Task<ActionResult<BookInfoDto>> GetBookInfo(int chapterId)
|
||||
{
|
||||
|
@ -64,8 +70,6 @@ namespace API.Controllers
|
|||
break;
|
||||
case MangaFormat.Unknown:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return Ok(new BookInfoDto()
|
||||
|
@ -83,6 +87,12 @@ namespace API.Controllers
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is an entry point to fetch resources from within an epub chapter/book.
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{chapterId}/book-resources")]
|
||||
public async Task<ActionResult> GetBookPageResources(int chapterId, [FromQuery] string file)
|
||||
{
|
||||
|
@ -102,7 +112,7 @@ namespace API.Controllers
|
|||
|
||||
/// <summary>
|
||||
/// This will return a list of mappings from ID -> page num. ID will be the xhtml key and page num will be the reading order
|
||||
/// this is used to rewrite anchors in the book text so that we always load properly in FE
|
||||
/// this is used to rewrite anchors in the book text so that we always load properly in our reader.
|
||||
/// </summary>
|
||||
/// <remarks>This is essentially building the table of contents</remarks>
|
||||
/// <param name="chapterId"></param>
|
||||
|
@ -229,6 +239,13 @@ namespace API.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns a single page within the epub book. All html will be rewritten to be scoped within our reader,
|
||||
/// all css is scoped, etc.
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <param name="page"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{chapterId}/book-page")]
|
||||
public async Task<ActionResult<string>> GetBookPage(int chapterId, [FromQuery] int page)
|
||||
{
|
||||
|
|
|
@ -18,6 +18,9 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace API.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// All APIs related to downloading entities from the system. Requires Download Role or Admin Role.
|
||||
/// </summary>
|
||||
[Authorize(Policy="RequireDownloadRole")]
|
||||
public class DownloadController : BaseApiController
|
||||
{
|
||||
|
@ -42,6 +45,11 @@ namespace API.Controllers
|
|||
_bookmarkService = bookmarkService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a given volume, return the size in bytes
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("volume-size")]
|
||||
public async Task<ActionResult<long>> GetVolumeSize(int volumeId)
|
||||
{
|
||||
|
@ -49,6 +57,11 @@ namespace API.Controllers
|
|||
return Ok(_directoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a given chapter, return the size in bytes
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("chapter-size")]
|
||||
public async Task<ActionResult<long>> GetChapterSize(int chapterId)
|
||||
{
|
||||
|
@ -56,6 +69,11 @@ namespace API.Controllers
|
|||
return Ok(_directoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a series, return the size in bytes
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("series-size")]
|
||||
public async Task<ActionResult<long>> GetSeriesSize(int seriesId)
|
||||
{
|
||||
|
@ -64,7 +82,11 @@ namespace API.Controllers
|
|||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Downloads all chapters within a volume.
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy="RequireDownloadRole")]
|
||||
[HttpGet("volume")]
|
||||
public async Task<ActionResult> DownloadVolume(int volumeId)
|
||||
|
|
|
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
namespace API.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for servicing up images stored in the DB
|
||||
/// Responsible for servicing up images stored in Kavita for entities
|
||||
/// </summary>
|
||||
public class ImageController : BaseApiController
|
||||
{
|
||||
|
|
|
@ -239,6 +239,12 @@ namespace API.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing Library with new name, folders, and/or type.
|
||||
/// </summary>
|
||||
/// <remarks>Any folder or type change will invoke a scan.</remarks>
|
||||
/// <param name="libraryForUserDto"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("update")]
|
||||
public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto libraryForUserDto)
|
||||
|
@ -250,10 +256,13 @@ namespace API.Controllers
|
|||
library.Name = libraryForUserDto.Name;
|
||||
library.Folders = libraryForUserDto.Folders.Select(s => new FolderPath() {Path = s}).ToList();
|
||||
|
||||
var typeUpdate = library.Type != libraryForUserDto.Type;
|
||||
library.Type = libraryForUserDto.Type;
|
||||
|
||||
_unitOfWork.LibraryRepository.Update(library);
|
||||
|
||||
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was a critical issue updating the library.");
|
||||
if (originalFolders.Count != libraryForUserDto.Folders.Count())
|
||||
if (originalFolders.Count != libraryForUserDto.Folders.Count() || typeUpdate)
|
||||
{
|
||||
_taskScheduler.ScanLibrary(library.Id);
|
||||
}
|
||||
|
|
|
@ -191,6 +191,11 @@ namespace API.Controllers
|
|||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Marks a Series as read. All volumes and chapters will be marked as read during this process.
|
||||
/// </summary>
|
||||
/// <param name="markReadDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("mark-read")]
|
||||
public async Task<ActionResult> MarkRead(MarkReadDto markReadDto)
|
||||
{
|
||||
|
@ -204,7 +209,7 @@ namespace API.Controllers
|
|||
|
||||
|
||||
/// <summary>
|
||||
/// Marks a Series as Unread (progress)
|
||||
/// Marks a Series as Unread. All volumes and chapters will be marked as unread during this process.
|
||||
/// </summary>
|
||||
/// <param name="markReadDto"></param>
|
||||
/// <returns></returns>
|
||||
|
|
|
@ -1,26 +1,41 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// All APIs are for Tachiyomi extension and app. They have hacks for our implementation and should not be used for any
|
||||
/// other purposes.
|
||||
/// </summary>
|
||||
public class TachiyomiController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IReaderService _readerService;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public TachiyomiController(IUnitOfWork unitOfWork, IReaderService readerService)
|
||||
public TachiyomiController(IUnitOfWork unitOfWork, IReaderService readerService, IMapper mapper)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_readerService = readerService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the series Id, this should return the latest chapter that has been fully read.
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns>ChapterDTO of latest chapter. Only Chapter number is used by consuming app. All other fields may be missing.</returns>
|
||||
[HttpGet("latest-chapter")]
|
||||
public async Task<ActionResult<ChapterDto>> GetLatestChapter(int seriesId)
|
||||
{
|
||||
|
@ -31,10 +46,45 @@ public class TachiyomiController : BaseApiController
|
|||
var prevChapterId =
|
||||
await _readerService.GetPrevChapterIdAsync(seriesId, currentChapter.VolumeId, currentChapter.Id, userId);
|
||||
|
||||
if (prevChapterId == -1) return null;
|
||||
// If prevChapterId is -1, this means either nothing is read or everything is read.
|
||||
if (prevChapterId == -1)
|
||||
{
|
||||
var userWithProgress = await _unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.Progress);
|
||||
var userHasProgress =
|
||||
userWithProgress.Progresses.Any(x => x.SeriesId == seriesId);
|
||||
|
||||
// If the user doesn't have progress, then return null, which the extension will catch as 204 (no content) and report nothing as read
|
||||
if (!userHasProgress) return null;
|
||||
|
||||
// Else return the max chapter to Tachiyomi so it can consider everything read
|
||||
var volumes = (await _unitOfWork.VolumeRepository.GetVolumes(seriesId)).ToImmutableList();
|
||||
var looseLeafChapterVolume = volumes.FirstOrDefault(v => v.Number == 0);
|
||||
if (looseLeafChapterVolume == null)
|
||||
{
|
||||
var volumeChapter = _mapper.Map<ChapterDto>(volumes.Last().Chapters.OrderBy(c => float.Parse(c.Number), new ChapterSortComparerZeroFirst()).Last());
|
||||
return Ok(new ChapterDto()
|
||||
{
|
||||
Number = $"{int.Parse(volumeChapter.Number) / 100f}"
|
||||
});
|
||||
}
|
||||
|
||||
var lastChapter = looseLeafChapterVolume.Chapters.OrderBy(c => float.Parse(c.Number), new ChapterSortComparer()).Last();
|
||||
return Ok(_mapper.Map<ChapterDto>(lastChapter));
|
||||
}
|
||||
|
||||
// There is progress, we now need to figure out the highest volume or chapter and return that.
|
||||
var prevChapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(prevChapterId);
|
||||
var volumeWithProgress = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(prevChapter.VolumeId, userId);
|
||||
if (volumeWithProgress.Number != 0)
|
||||
{
|
||||
// The progress is on a volume, encode it as a fake chapterDTO
|
||||
return Ok(new ChapterDto()
|
||||
{
|
||||
Number = $"{volumeWithProgress.Number / 100f}"
|
||||
});
|
||||
}
|
||||
|
||||
// Progress is just on a chapter, return as is
|
||||
return Ok(prevChapter);
|
||||
}
|
||||
|
||||
|
@ -51,8 +101,9 @@ public class TachiyomiController : BaseApiController
|
|||
|
||||
switch (chapterNumber)
|
||||
{
|
||||
// Tachiyomi sends chapter 0.0f when there's no chapters read.
|
||||
// Due to the encoding for volumes this marks all chapters in volume 0 (loose chapters) as read so we ignore it
|
||||
// When Tachiyomi sync's progress, if there is no current progress in Tachiyomi, 0.0f is sent.
|
||||
// Due to the encoding for volumes, this marks all chapters in volume 0 (loose chapters) as read.
|
||||
// Hence we catch and return early, so we ignore the request.
|
||||
case 0.0f:
|
||||
return true;
|
||||
case < 1.0f:
|
||||
|
|
|
@ -95,6 +95,7 @@ namespace API.Controllers
|
|||
existingPreferences.BookReaderLayoutMode = preferencesDto.BookReaderLayoutMode;
|
||||
existingPreferences.BookReaderImmersiveMode = preferencesDto.BookReaderImmersiveMode;
|
||||
existingPreferences.GlobalPageLayoutMode = preferencesDto.GlobalPageLayoutMode;
|
||||
existingPreferences.BlurUnreadSummaries = preferencesDto.BlurUnreadSummaries;
|
||||
existingPreferences.Theme = await _unitOfWork.SiteThemeRepository.GetThemeById(preferencesDto.Theme.Id);
|
||||
|
||||
// TODO: Remove this code - this overrides layout mode to be single until the mode is released
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue