mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Release Testing Day 3 (#1951)
* Code cleanup. Fixed OPDS images missing api key. Fixed theme color on site manifest not being black. * Removed a console.log from timeago pipe * Reading list page is now alphabetical and the modal for adding to a reading list is ordered by most recent. * Fixed a bug where remove read from reading list failed due to Calculating Start and End date assuming chapter would always be there. * Fixed a bug where reading list cover would get reset when editing the reading list. * Fixed a bug where reading list item didn't have not read badge. It's on old style. * Fixed a bug where user-preferences was hitting an admin only api when there was a better alternative * Slight memory improvement on a common db call * Fixed a bug where resetting to default theme when a theme was deleted was throwing an exception and failing. * All Login dtos now have the active KavitaVersion to make external apps able to handle what version of the API they are connecting with. * Fixed up a case where getVolume repo method always assumed there was a volume by that Id.
This commit is contained in:
parent
875931b162
commit
faf58e6985
@ -18,7 +18,7 @@ namespace API.Benchmark;
|
|||||||
public class EpubBenchmark
|
public class EpubBenchmark
|
||||||
{
|
{
|
||||||
private const string FilePath = @"E:\Books\Invaders of the Rokujouma\Invaders of the Rokujouma - Volume 01.epub";
|
private const string FilePath = @"E:\Books\Invaders of the Rokujouma\Invaders of the Rokujouma - Volume 01.epub";
|
||||||
private readonly Regex WordRegex = new Regex(@"\b\w+\b", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
private readonly Regex _wordRegex = new Regex(@"\b\w+\b", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public async Task GetWordCount_PassByRef()
|
public async Task GetWordCount_PassByRef()
|
||||||
@ -100,6 +100,6 @@ public class EpubBenchmark
|
|||||||
|
|
||||||
|
|
||||||
return doc.DocumentNode.SelectNodes("//body//text()[not(parent::script)]")
|
return doc.DocumentNode.SelectNodes("//body//text()[not(parent::script)]")
|
||||||
.Sum(node => WordRegex.Matches(node.InnerText).Count);
|
.Sum(node => _wordRegex.Matches(node.InnerText).Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,28 +45,28 @@ public class ProcessSeriesTests
|
|||||||
|
|
||||||
#region UpdateChapterFromComicInfo
|
#region UpdateChapterFromComicInfo
|
||||||
|
|
||||||
public void UpdateChapterFromComicInfo_()
|
// public void UpdateChapterFromComicInfo_()
|
||||||
{
|
// {
|
||||||
// TODO: Do this
|
// // TODO: Do this
|
||||||
var file = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ScannerService/Library/Manga/Hajime no Ippo/Hajime no Ippo Chapter 1.cbz");
|
// var file = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ScannerService/Library/Manga/Hajime no Ippo/Hajime no Ippo Chapter 1.cbz");
|
||||||
// Chapter and ComicInfo
|
// // Chapter and ComicInfo
|
||||||
var chapter = new ChapterBuilder("1")
|
// var chapter = new ChapterBuilder("1")
|
||||||
.WithId(0)
|
// .WithId(0)
|
||||||
.WithFile(new MangaFileBuilder(file, MangaFormat.Archive).Build())
|
// .WithFile(new MangaFileBuilder(file, MangaFormat.Archive).Build())
|
||||||
.Build();
|
// .Build();
|
||||||
|
//
|
||||||
var ps = new ProcessSeries(Substitute.For<IUnitOfWork>(), Substitute.For<ILogger<ProcessSeries>>(),
|
// var ps = new ProcessSeries(Substitute.For<IUnitOfWork>(), Substitute.For<ILogger<ProcessSeries>>(),
|
||||||
Substitute.For<IEventHub>(), Substitute.For<IDirectoryService>()
|
// Substitute.For<IEventHub>(), Substitute.For<IDirectoryService>()
|
||||||
, Substitute.For<ICacheHelper>(), Substitute.For<IReadingItemService>(), Substitute.For<IFileService>(),
|
// , Substitute.For<ICacheHelper>(), Substitute.For<IReadingItemService>(), Substitute.For<IFileService>(),
|
||||||
Substitute.For<IMetadataService>(),
|
// Substitute.For<IMetadataService>(),
|
||||||
Substitute.For<IWordCountAnalyzerService>(),
|
// Substitute.For<IWordCountAnalyzerService>(),
|
||||||
Substitute.For<ICollectionTagService>(), Substitute.For<IReadingListService>());
|
// Substitute.For<ICollectionTagService>(), Substitute.For<IReadingListService>());
|
||||||
|
//
|
||||||
ps.UpdateChapterFromComicInfo(chapter, new ComicInfo()
|
// ps.UpdateChapterFromComicInfo(chapter, new ComicInfo()
|
||||||
{
|
// {
|
||||||
|
//
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,8 @@ public class AccountController : BaseApiController
|
|||||||
Token = await _tokenService.CreateToken(user),
|
Token = await _tokenService.CreateToken(user),
|
||||||
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
||||||
ApiKey = user.ApiKey,
|
ApiKey = user.ApiKey,
|
||||||
Preferences = _mapper.Map<UserPreferencesDto>(user.UserPreferences)
|
Preferences = _mapper.Map<UserPreferencesDto>(user.UserPreferences),
|
||||||
|
KavitaVersion = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion)).Value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -213,6 +214,8 @@ public class AccountController : BaseApiController
|
|||||||
var dto = _mapper.Map<UserDto>(user);
|
var dto = _mapper.Map<UserDto>(user);
|
||||||
dto.Token = await _tokenService.CreateToken(user);
|
dto.Token = await _tokenService.CreateToken(user);
|
||||||
dto.RefreshToken = await _tokenService.CreateRefreshToken(user);
|
dto.RefreshToken = await _tokenService.CreateRefreshToken(user);
|
||||||
|
dto.KavitaVersion = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion))
|
||||||
|
.Value;
|
||||||
var pref = await _unitOfWork.UserRepository.GetPreferencesAsync(user.UserName!);
|
var pref = await _unitOfWork.UserRepository.GetPreferencesAsync(user.UserName!);
|
||||||
if (pref == null) return Ok(dto);
|
if (pref == null) return Ok(dto);
|
||||||
|
|
||||||
@ -687,7 +690,8 @@ public class AccountController : BaseApiController
|
|||||||
Token = await _tokenService.CreateToken(user),
|
Token = await _tokenService.CreateToken(user),
|
||||||
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
||||||
ApiKey = user.ApiKey,
|
ApiKey = user.ApiKey,
|
||||||
Preferences = _mapper.Map<UserPreferencesDto>(user.UserPreferences)
|
Preferences = _mapper.Map<UserPreferencesDto>(user.UserPreferences),
|
||||||
|
KavitaVersion = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion)).Value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -840,7 +844,8 @@ public class AccountController : BaseApiController
|
|||||||
Token = await _tokenService.CreateToken(user),
|
Token = await _tokenService.CreateToken(user),
|
||||||
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
||||||
ApiKey = user.ApiKey,
|
ApiKey = user.ApiKey,
|
||||||
Preferences = _mapper.Map<UserPreferencesDto>(user.UserPreferences)
|
Preferences = _mapper.Map<UserPreferencesDto>(user.UserPreferences),
|
||||||
|
KavitaVersion = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion)).Value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,8 +231,8 @@ public class OpdsController : BaseApiController
|
|||||||
Links = new List<FeedLink>()
|
Links = new List<FeedLink>()
|
||||||
{
|
{
|
||||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/collections/{tag.Id}"),
|
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/collections/{tag.Id}"),
|
||||||
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/collection-cover?collectionId={tag.Id}"),
|
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/collection-cover?collectionId={tag.Id}&apiKey={apiKey}"),
|
||||||
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/collection-cover?collectionId={tag.Id}")
|
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/collection-cover?collectionId={tag.Id}&apiKey={apiKey}")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -294,7 +294,8 @@ public class OpdsController : BaseApiController
|
|||||||
var (baseUrl, prefix) = await GetPrefix();
|
var (baseUrl, prefix) = await GetPrefix();
|
||||||
var userId = await GetUser(apiKey);
|
var userId = await GetUser(apiKey);
|
||||||
|
|
||||||
var readingLists = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, true, GetUserParams(pageNumber));
|
var readingLists = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId,
|
||||||
|
true, GetUserParams(pageNumber), false);
|
||||||
|
|
||||||
|
|
||||||
var feed = CreateFeed("All Reading Lists", $"{prefix}{apiKey}/reading-list", apiKey, prefix, baseUrl);
|
var feed = CreateFeed("All Reading Lists", $"{prefix}{apiKey}/reading-list", apiKey, prefix, baseUrl);
|
||||||
@ -480,9 +481,9 @@ public class OpdsController : BaseApiController
|
|||||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
|
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
|
||||||
$"{prefix}{apiKey}/collections/{collection.Id}"),
|
$"{prefix}{apiKey}/collections/{collection.Id}"),
|
||||||
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image,
|
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image,
|
||||||
$"{baseUrl}api/image/collection-cover?collectionId={collection.Id}"),
|
$"{baseUrl}api/image/collection-cover?collectionId={collection.Id}&apiKey={apiKey}"),
|
||||||
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image,
|
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image,
|
||||||
$"{baseUrl}api/image/collection-cover?collectionId={collection.Id}")
|
$"{baseUrl}api/image/collection-cover?collectionId={collection.Id}&apiKey={apiKey}")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -546,7 +547,7 @@ public class OpdsController : BaseApiController
|
|||||||
|
|
||||||
var feed = CreateFeed(series.Name + " - Storyline", $"{prefix}{apiKey}/series/{series.Id}", apiKey, prefix, baseUrl);
|
var feed = CreateFeed(series.Name + " - Storyline", $"{prefix}{apiKey}/series/{series.Id}", apiKey, prefix, baseUrl);
|
||||||
SetFeedId(feed, $"series-{series.Id}");
|
SetFeedId(feed, $"series-{series.Id}");
|
||||||
feed.Links.Add(CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={seriesId}"));
|
feed.Links.Add(CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={seriesId}&apiKey={apiKey}"));
|
||||||
|
|
||||||
var seriesDetail = await _seriesService.GetSeriesDetail(seriesId, userId);
|
var seriesDetail = await _seriesService.GetSeriesDetail(seriesId, userId);
|
||||||
foreach (var volume in seriesDetail.Volumes)
|
foreach (var volume in seriesDetail.Volumes)
|
||||||
@ -737,8 +738,8 @@ public class OpdsController : BaseApiController
|
|||||||
Links = new List<FeedLink>()
|
Links = new List<FeedLink>()
|
||||||
{
|
{
|
||||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/series/{seriesDto.Id}"),
|
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/series/{seriesDto.Id}"),
|
||||||
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={seriesDto.Id}"),
|
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={seriesDto.Id}&apiKey={apiKey}"),
|
||||||
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={seriesDto.Id}")
|
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={seriesDto.Id}&apiKey={apiKey}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -752,8 +753,8 @@ public class OpdsController : BaseApiController
|
|||||||
Links = new List<FeedLink>()
|
Links = new List<FeedLink>()
|
||||||
{
|
{
|
||||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/series/{searchResultDto.SeriesId}"),
|
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/series/{searchResultDto.SeriesId}"),
|
||||||
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={searchResultDto.SeriesId}"),
|
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={searchResultDto.SeriesId}&apiKey={apiKey}"),
|
||||||
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={searchResultDto.SeriesId}")
|
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={searchResultDto.SeriesId}&apiKey={apiKey}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -770,9 +771,9 @@ public class OpdsController : BaseApiController
|
|||||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
|
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
|
||||||
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}"),
|
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}"),
|
||||||
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image,
|
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image,
|
||||||
$"{baseUrl}api/image/chapter-cover?chapterId={chapterId}"),
|
$"{baseUrl}api/image/chapter-cover?chapterId={chapterId}&apiKey={apiKey}"),
|
||||||
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image,
|
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image,
|
||||||
$"{baseUrl}api/image/chapter-cover?chapterId={chapterId}")
|
$"{baseUrl}api/image/chapter-cover?chapterId={chapterId}&apiKey={apiKey}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -788,9 +789,10 @@ public class OpdsController : BaseApiController
|
|||||||
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
|
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
|
||||||
var volume = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, await GetUser(apiKey));
|
var volume = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, await GetUser(apiKey));
|
||||||
|
|
||||||
|
|
||||||
var title = $"{series.Name}";
|
var title = $"{series.Name}";
|
||||||
|
|
||||||
if (volume.Chapters.Count == 1)
|
if (volume!.Chapters.Count == 1)
|
||||||
{
|
{
|
||||||
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType);
|
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType);
|
||||||
if (volume.Name != "0")
|
if (volume.Name != "0")
|
||||||
@ -823,8 +825,8 @@ public class OpdsController : BaseApiController
|
|||||||
Format = mangaFile.Format.ToString(),
|
Format = mangaFile.Format.ToString(),
|
||||||
Links = new List<FeedLink>()
|
Links = new List<FeedLink>()
|
||||||
{
|
{
|
||||||
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/chapter-cover?chapterId={chapterId}"),
|
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/chapter-cover?chapterId={chapterId}&apiKey={apiKey}"),
|
||||||
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/chapter-cover?chapterId={chapterId}"),
|
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/chapter-cover?chapterId={chapterId}&apiKey={apiKey}"),
|
||||||
// We can't not include acc link in the feed, panels doesn't work with just page streaming option. We have to block download directly
|
// We can't not include acc link in the feed, panels doesn't work with just page streaming option. We have to block download directly
|
||||||
accLink,
|
accLink,
|
||||||
await CreatePageStreamLink(series.LibraryId, seriesId, volumeId, chapterId, mangaFile, apiKey, prefix)
|
await CreatePageStreamLink(series.LibraryId, seriesId, volumeId, chapterId, mangaFile, apiKey, prefix)
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
|
using API.Entities.Enums;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
|
using Kavita.Common;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -45,6 +47,7 @@ public class PluginController : BaseApiController
|
|||||||
Token = await _tokenService.CreateToken(user),
|
Token = await _tokenService.CreateToken(user),
|
||||||
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
||||||
ApiKey = user.ApiKey,
|
ApiKey = user.ApiKey,
|
||||||
|
KavitaVersion = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion)).Value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,13 +47,15 @@ public class ReadingListController : BaseApiController
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="includePromoted">Include Promoted Reading Lists along with user's Reading Lists. Defaults to true</param>
|
/// <param name="includePromoted">Include Promoted Reading Lists along with user's Reading Lists. Defaults to true</param>
|
||||||
/// <param name="userParams">Pagination parameters</param>
|
/// <param name="userParams">Pagination parameters</param>
|
||||||
|
/// <param name="sortByLastModified">Sort by last modified (most recent first) or by title (alphabetical)</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost("lists")]
|
[HttpPost("lists")]
|
||||||
public async Task<ActionResult<IEnumerable<ReadingListDto>>> GetListsForUser([FromQuery] UserParams userParams, bool includePromoted = true)
|
public async Task<ActionResult<IEnumerable<ReadingListDto>>> GetListsForUser([FromQuery] UserParams userParams,
|
||||||
|
bool includePromoted = true, bool sortByLastModified = false)
|
||||||
{
|
{
|
||||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||||
var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, includePromoted,
|
var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, includePromoted,
|
||||||
userParams);
|
userParams, sortByLastModified);
|
||||||
Response.AddPaginationHeader(items.CurrentPage, items.PageSize, items.TotalCount, items.TotalPages);
|
Response.AddPaginationHeader(items.CurrentPage, items.PageSize, items.TotalCount, items.TotalPages);
|
||||||
|
|
||||||
return Ok(items);
|
return Ok(items);
|
||||||
|
@ -111,7 +111,7 @@ public class SeriesController : BaseApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("volume")]
|
[HttpGet("volume")]
|
||||||
public async Task<ActionResult<VolumeDto>> GetVolume(int volumeId)
|
public async Task<ActionResult<VolumeDto?>> GetVolume(int volumeId)
|
||||||
{
|
{
|
||||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||||
return Ok(await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, userId));
|
return Ok(await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, userId));
|
||||||
|
@ -53,19 +53,6 @@ public class ServerController : BaseApiController
|
|||||||
_taskScheduler = taskScheduler;
|
_taskScheduler = taskScheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to Restart the server. Does not work, will shutdown the instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpPost("restart")]
|
|
||||||
public ActionResult RestartServer()
|
|
||||||
{
|
|
||||||
_logger.LogInformation("{UserName} is restarting server from admin dashboard", User.GetUsername());
|
|
||||||
|
|
||||||
_applicationLifetime.StopApplication();
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs an ad-hoc cleanup of Cache
|
/// Performs an ad-hoc cleanup of Cache
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -45,7 +45,6 @@ public class SettingsController : BaseApiController
|
|||||||
_libraryWatcher = libraryWatcher;
|
_libraryWatcher = libraryWatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
[AllowAnonymous]
|
|
||||||
[HttpGet("base-url")]
|
[HttpGet("base-url")]
|
||||||
public async Task<ActionResult<string>> GetBaseUrl()
|
public async Task<ActionResult<string>> GetBaseUrl()
|
||||||
{
|
{
|
||||||
|
@ -12,4 +12,5 @@ public class UserDto
|
|||||||
public string? ApiKey { get; init; }
|
public string? ApiKey { get; init; }
|
||||||
public UserPreferencesDto? Preferences { get; set; }
|
public UserPreferencesDto? Preferences { get; set; }
|
||||||
public AgeRestrictionDto? AgeRestriction { get; init; }
|
public AgeRestrictionDto? AgeRestriction { get; init; }
|
||||||
|
public string KavitaVersion { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ public enum ReadingListIncludes
|
|||||||
|
|
||||||
public interface IReadingListRepository
|
public interface IReadingListRepository
|
||||||
{
|
{
|
||||||
Task<PagedList<ReadingListDto>> GetReadingListDtosForUserAsync(int userId, bool includePromoted, UserParams userParams);
|
Task<PagedList<ReadingListDto>> GetReadingListDtosForUserAsync(int userId, bool includePromoted, UserParams userParams, bool sortByLastModified = true);
|
||||||
Task<ReadingList?> GetReadingListByIdAsync(int readingListId, ReadingListIncludes includes = ReadingListIncludes.None);
|
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);
|
||||||
Task<ReadingListDto?> GetReadingListDtoByIdAsync(int readingListId, int userId);
|
Task<ReadingListDto?> GetReadingListDtoByIdAsync(int readingListId, int userId);
|
||||||
@ -166,17 +166,18 @@ public class ReadingListRepository : IReadingListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<PagedList<ReadingListDto>> GetReadingListDtosForUserAsync(int userId, bool includePromoted, UserParams userParams)
|
public async Task<PagedList<ReadingListDto>> GetReadingListDtosForUserAsync(int userId, bool includePromoted, UserParams userParams, bool sortByLastModified = true)
|
||||||
{
|
{
|
||||||
var userAgeRating = (await _context.AppUser.SingleAsync(u => u.Id == userId)).AgeRestriction;
|
var userAgeRating = (await _context.AppUser.SingleAsync(u => u.Id == userId)).AgeRestriction;
|
||||||
var query = _context.ReadingList
|
var query = _context.ReadingList
|
||||||
.Where(l => l.AppUserId == userId || (includePromoted && l.Promoted ))
|
.Where(l => l.AppUserId == userId || (includePromoted && l.Promoted ))
|
||||||
.Where(l => l.AgeRating >= userAgeRating)
|
.Where(l => l.AgeRating >= userAgeRating);
|
||||||
.OrderBy(l => l.LastModified)
|
query = sortByLastModified ? query.OrderByDescending(l => l.LastModified) : query.OrderBy(l => l.NormalizedTitle);
|
||||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
|
||||||
|
var finalQuery = query.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||||
.AsNoTracking();
|
.AsNoTracking();
|
||||||
|
|
||||||
return await PagedList<ReadingListDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
return await PagedList<ReadingListDto>.CreateAsync(finalQuery, userParams.PageNumber, userParams.PageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<ReadingListDto>> GetReadingListDtosForSeriesAndUserAsync(int userId, int seriesId, bool includePromoted)
|
public async Task<IEnumerable<ReadingListDto>> GetReadingListDtosForSeriesAndUserAsync(int userId, int seriesId, bool includePromoted)
|
||||||
|
@ -140,10 +140,8 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
private readonly DataContext _context;
|
private readonly DataContext _context;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
private readonly Regex _yearRegex = new Regex(@"\d{4}", RegexOptions.Compiled,
|
||||||
// [GeneratedRegex(@"\d{4}", RegexOptions.Compiled, 50000)]
|
Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||||
// private static partial Regex YearRegex();
|
|
||||||
private readonly Regex _yearRegex = new Regex(@"\d{4}", RegexOptions.Compiled, Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
|
|
||||||
|
|
||||||
public SeriesRepository(DataContext context, IMapper mapper)
|
public SeriesRepository(DataContext context, IMapper mapper)
|
||||||
{
|
{
|
||||||
|
@ -71,7 +71,7 @@ public class SiteThemeRepository : ISiteThemeRepository
|
|||||||
{
|
{
|
||||||
var result = await _context.SiteTheme
|
var result = await _context.SiteTheme
|
||||||
.Where(t => t.IsDefault)
|
.Where(t => t.IsDefault)
|
||||||
.SingleOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
|
@ -337,7 +337,7 @@ public class UserRepository : IUserRepository
|
|||||||
return await _context.AppUser
|
return await _context.AppUser
|
||||||
.Where(u => u.ApiKey != null && u.ApiKey.Equals(apiKey))
|
.Where(u => u.ApiKey != null && u.ApiKey.Equals(apiKey))
|
||||||
.Select(u => u.Id)
|
.Select(u => u.Id)
|
||||||
.SingleOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
@ -21,7 +22,7 @@ public interface IVolumeRepository
|
|||||||
Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds);
|
Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds);
|
||||||
Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId);
|
Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId);
|
||||||
Task<Volume?> GetVolumeAsync(int volumeId);
|
Task<Volume?> GetVolumeAsync(int volumeId);
|
||||||
Task<VolumeDto> GetVolumeDtoAsync(int volumeId, int userId);
|
Task<VolumeDto?> GetVolumeDtoAsync(int volumeId, int userId);
|
||||||
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> seriesIds, bool includeChapters = false);
|
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> seriesIds, bool includeChapters = false);
|
||||||
Task<IEnumerable<Volume>> GetVolumes(int seriesId);
|
Task<IEnumerable<Volume>> GetVolumes(int seriesId);
|
||||||
Task<Volume?> GetVolumeByIdAsync(int volumeId);
|
Task<Volume?> GetVolumeByIdAsync(int volumeId);
|
||||||
@ -119,7 +120,7 @@ public class VolumeRepository : IVolumeRepository
|
|||||||
/// <param name="volumeId"></param>
|
/// <param name="volumeId"></param>
|
||||||
/// <param name="userId"></param>
|
/// <param name="userId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<VolumeDto> GetVolumeDtoAsync(int volumeId, int userId)
|
public async Task<VolumeDto?> GetVolumeDtoAsync(int volumeId, int userId)
|
||||||
{
|
{
|
||||||
var volume = await _context.Volume
|
var volume = await _context.Volume
|
||||||
.Where(vol => vol.Id == volumeId)
|
.Where(vol => vol.Id == volumeId)
|
||||||
@ -127,7 +128,9 @@ public class VolumeRepository : IVolumeRepository
|
|||||||
.ThenInclude(c => c.Files)
|
.ThenInclude(c => c.Files)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||||
.SingleAsync(vol => vol.Id == volumeId);
|
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
|
||||||
|
|
||||||
|
if (volume == null) return null;
|
||||||
|
|
||||||
var volumeList = new List<VolumeDto>() {volume};
|
var volumeList = new List<VolumeDto>() {volume};
|
||||||
await AddVolumeModifiers(userId, volumeList);
|
await AddVolumeModifiers(userId, volumeList);
|
||||||
|
@ -20,8 +20,6 @@ public static class RestrictByAgeExtensions
|
|||||||
return q.Where(s => s.Metadata.AgeRating != AgeRating.Unknown);
|
return q.Where(s => s.Metadata.AgeRating != AgeRating.Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
//q.WhereIf(!restriction.IncludeUnknowns, s => s.Metadata.AgeRating != AgeRating.Unknown);
|
|
||||||
|
|
||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,6 @@ namespace API.Extensions;
|
|||||||
|
|
||||||
public static class StringExtensions
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
// Wait for Rosyln bugfix
|
|
||||||
// [GeneratedRegex(@"(^[a-z])|\.\s+(.)", RegexOptions.ExplicitCapture | RegexOptions.Compiled)]
|
|
||||||
// private static partial Regex SentenceCaseRegex();
|
|
||||||
private static readonly Regex SentenceCaseRegex = new Regex(@"(^[a-z])|\.\s+(.)",
|
private static readonly Regex SentenceCaseRegex = new Regex(@"(^[a-z])|\.\s+(.)",
|
||||||
RegexOptions.ExplicitCapture | RegexOptions.Compiled, Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
|
RegexOptions.ExplicitCapture | RegexOptions.Compiled, Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||||
|
|
||||||
|
@ -72,27 +72,6 @@ public class BookService : IBookService
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use when Rosyln fixed
|
|
||||||
// [GeneratedRegex(@"/\*[\d\D]*?\*/", RegexOptions.Compiled)]
|
|
||||||
// private static partial Regex CssComment();
|
|
||||||
//
|
|
||||||
// [GeneratedRegex(@"[a-zA-Z]+#", RegexOptions.Compiled)]
|
|
||||||
// private static partial Regex WhiteSpace1();
|
|
||||||
// [GeneratedRegex(@"[\n\r]+\s*", RegexOptions.Compiled)]
|
|
||||||
// private static partial Regex WhiteSpace2();
|
|
||||||
// [GeneratedRegex(@"\s+", RegexOptions.Compiled)]
|
|
||||||
// private static partial Regex WhiteSpace3();
|
|
||||||
// [GeneratedRegex(@"\s?([:,;{}])\s?", RegexOptions.Compiled)]
|
|
||||||
// private static partial Regex WhiteSpace4();
|
|
||||||
// [GeneratedRegex(@"([\s:]0)(px|pt|%|em)", RegexOptions.Compiled)]
|
|
||||||
// private static partial Regex UnitPadding();
|
|
||||||
//
|
|
||||||
// [GeneratedRegex(@"<script(.*)(/>)", RegexOptions.Compiled)]
|
|
||||||
// private static partial Regex StartingScriptTag();
|
|
||||||
// [GeneratedRegex(@"<title(.*)(/>)", RegexOptions.Compiled)]
|
|
||||||
// private static partial Regex StartingTitleTag();
|
|
||||||
|
|
||||||
|
|
||||||
public BookService(ILogger<BookService> logger, IDirectoryService directoryService, IImageService imageService)
|
public BookService(ILogger<BookService> logger, IDirectoryService directoryService, IImageService imageService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -287,7 +266,6 @@ public class BookService : IBookService
|
|||||||
|
|
||||||
if (images == null) return;
|
if (images == null) return;
|
||||||
|
|
||||||
|
|
||||||
var parent = images.First().ParentNode;
|
var parent = images.First().ParentNode;
|
||||||
|
|
||||||
foreach (var image in images)
|
foreach (var image in images)
|
||||||
|
@ -76,23 +76,14 @@ public class DirectoryService : IDirectoryService
|
|||||||
public string BookmarkDirectory { get; }
|
public string BookmarkDirectory { get; }
|
||||||
public string SiteThemeDirectory { get; }
|
public string SiteThemeDirectory { get; }
|
||||||
private readonly ILogger<DirectoryService> _logger;
|
private readonly ILogger<DirectoryService> _logger;
|
||||||
private const int RegexTimeoutMs = 5000000;
|
|
||||||
private const RegexOptions MatchOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase;
|
private const RegexOptions MatchOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase;
|
||||||
|
|
||||||
// [GeneratedRegex(@"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle",
|
|
||||||
// MatchOptions, matchTimeoutMilliseconds: RegexTimeoutMs)]
|
|
||||||
// private static partial Regex ExcludeDirectoriesRegex();
|
|
||||||
//
|
|
||||||
// [GeneratedRegex(@"\(\d+\)",
|
|
||||||
// MatchOptions, matchTimeoutMilliseconds: RegexTimeoutMs)]
|
|
||||||
// private static partial Regex FileCopyAppendRegex();
|
|
||||||
|
|
||||||
private static readonly Regex ExcludeDirectories = new Regex(
|
private static readonly Regex ExcludeDirectories = new Regex(
|
||||||
@"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle|\.@__thumb",
|
@"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle|\.@__thumb",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase,
|
MatchOptions,
|
||||||
Tasks.Scanner.Parser.Parser.RegexTimeout);
|
Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||||
private static readonly Regex FileCopyAppend = new Regex(@"\(\d+\)",
|
private static readonly Regex FileCopyAppend = new Regex(@"\(\d+\)",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase,
|
MatchOptions,
|
||||||
Tasks.Scanner.Parser.Parser.RegexTimeout);
|
Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||||
public static readonly string BackupDirectory = Path.Join(Directory.GetCurrentDirectory(), "config", "backups");
|
public static readonly string BackupDirectory = Path.Join(Directory.GetCurrentDirectory(), "config", "backups");
|
||||||
|
|
||||||
|
@ -260,7 +260,6 @@ public class ImageService : IImageService
|
|||||||
|
|
||||||
public static string CreateMergedImage(List<string> coverImages, string dest)
|
public static string CreateMergedImage(List<string> coverImages, string dest)
|
||||||
{
|
{
|
||||||
// TODO: Needs testing
|
|
||||||
// Currently this doesn't work due to non-standard cover image sizes and dimensions
|
// Currently this doesn't work due to non-standard cover image sizes and dimensions
|
||||||
var image = Image.Black(320*4, 160*4);
|
var image = Image.Black(320*4, 160*4);
|
||||||
|
|
||||||
|
@ -273,7 +273,8 @@ public class ReaderService : IReaderService
|
|||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
|
await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
|
||||||
MessageFactory.UserProgressUpdateEvent(userId, user!.UserName!, progressDto.SeriesId, progressDto.VolumeId, progressDto.ChapterId, progressDto.PageNum));
|
MessageFactory.UserProgressUpdateEvent(userId, user!.UserName!, progressDto.SeriesId,
|
||||||
|
progressDto.VolumeId, progressDto.ChapterId, progressDto.PageNum));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ public interface IReadingListService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Methods responsible for management of Reading Lists
|
/// Methods responsible for management of Reading Lists
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>If called from API layer, expected for <see cref="UserHasReadingListAccess"/> to be called beforehand</remarks>
|
/// <remarks>If called from API layer, expected for <see cref="UserHasReadingListAccess(int, String)"/> to be called beforehand</remarks>
|
||||||
public class ReadingListService : IReadingListService
|
public class ReadingListService : IReadingListService
|
||||||
{
|
{
|
||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
@ -152,7 +152,7 @@ public class ReadingListService : IReadingListService
|
|||||||
|
|
||||||
readingList.Summary = dto.Summary;
|
readingList.Summary = dto.Summary;
|
||||||
readingList.Title = dto.Title.Trim();
|
readingList.Title = dto.Title.Trim();
|
||||||
readingList.NormalizedTitle = Tasks.Scanner.Parser.Parser.Normalize(readingList.Title);
|
readingList.NormalizedTitle = Parser.Normalize(readingList.Title);
|
||||||
readingList.Promoted = dto.Promoted;
|
readingList.Promoted = dto.Promoted;
|
||||||
readingList.CoverImageLocked = dto.CoverImageLocked;
|
readingList.CoverImageLocked = dto.CoverImageLocked;
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ public class ReadingListService : IReadingListService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes all entries that are fully read from the reading list. This commits
|
/// Removes all entries that are fully read from the reading list. This commits
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>If called from API layer, expected for <see cref="UserHasReadingListAccess"/> to be called beforehand</remarks>
|
/// <remarks>If called from API layer, expected for <see cref="UserHasReadingListAccess(int, String)"/> to be called beforehand</remarks>
|
||||||
/// <param name="readingListId">Reading List Id</param>
|
/// <param name="readingListId">Reading List Id</param>
|
||||||
/// <param name="user">User</param>
|
/// <param name="user">User</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
@ -203,8 +203,9 @@ public class ReadingListService : IReadingListService
|
|||||||
items = await _unitOfWork.ReadingListRepository.AddReadingProgressModifiers(user.Id, items.ToList());
|
items = await _unitOfWork.ReadingListRepository.AddReadingProgressModifiers(user.Id, items.ToList());
|
||||||
|
|
||||||
// Collect all Ids to remove
|
// Collect all Ids to remove
|
||||||
var itemIdsToRemove = items.Where(item => item.PagesRead == item.PagesTotal).Select(item => item.Id);
|
var itemIdsToRemove = items.Where(item => item.PagesRead == item.PagesTotal).Select(item => item.Id).ToList();
|
||||||
|
|
||||||
|
if (!itemIdsToRemove.Any()) return true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var listItems =
|
var listItems =
|
||||||
@ -218,7 +219,6 @@ public class ReadingListService : IReadingListService
|
|||||||
await CalculateStartAndEndDates(readingList);
|
await CalculateStartAndEndDates(readingList);
|
||||||
|
|
||||||
if (!_unitOfWork.HasChanges()) return true;
|
if (!_unitOfWork.HasChanges()) return true;
|
||||||
|
|
||||||
return await _unitOfWork.CommitAsync();
|
return await _unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@ -313,8 +313,8 @@ public class ReadingListService : IReadingListService
|
|||||||
_logger.LogError("Tried to calculate release dates for Reading List, but missing Chapter entities");
|
_logger.LogError("Tried to calculate release dates for Reading List, but missing Chapter entities");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var maxReleaseDate = items.Max(item => item.Chapter.ReleaseDate);
|
var maxReleaseDate = items.Where(item => item.Chapter != null).Max(item => item.Chapter.ReleaseDate);
|
||||||
var minReleaseDate = items.Min(item => item.Chapter.ReleaseDate);
|
var minReleaseDate = items.Where(item => item.Chapter != null).Min(item => item.Chapter.ReleaseDate);
|
||||||
if (maxReleaseDate != DateTime.MinValue)
|
if (maxReleaseDate != DateTime.MinValue)
|
||||||
{
|
{
|
||||||
readingListWithItems.EndingMonth = maxReleaseDate.Month;
|
readingListWithItems.EndingMonth = maxReleaseDate.Month;
|
||||||
|
@ -97,7 +97,7 @@ public class TachiyomiService : ITachiyomiService
|
|||||||
|
|
||||||
var volumeWithProgress = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(prevChapter.VolumeId, userId);
|
var volumeWithProgress = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(prevChapter.VolumeId, userId);
|
||||||
// We only encode for single-file volumes
|
// We only encode for single-file volumes
|
||||||
if (volumeWithProgress.Number != 0 && volumeWithProgress.Chapters.Count == 1)
|
if (volumeWithProgress!.Number != 0 && volumeWithProgress.Chapters.Count == 1)
|
||||||
{
|
{
|
||||||
// The progress is on a volume, encode it as a fake chapterDTO
|
// The progress is on a volume, encode it as a fake chapterDTO
|
||||||
return new ChapterDto()
|
return new ChapterDto()
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
"TokenKey": "super secret unguessable key",
|
"TokenKey": "super secret unguessable key",
|
||||||
"Port": 5000,
|
"Port": 5000,
|
||||||
"IpAddresses": "",
|
"IpAddresses": "",
|
||||||
"BaseUrl": "/test/"
|
"BaseUrl": "/"
|
||||||
}
|
}
|
@ -24,11 +24,12 @@ export class ReadingListService {
|
|||||||
return this.httpClient.get<ReadingList>(this.baseUrl + 'readinglist?readingListId=' + readingListId);
|
return this.httpClient.get<ReadingList>(this.baseUrl + 'readinglist?readingListId=' + readingListId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadingLists(includePromoted: boolean = true, pageNum?: number, itemsPerPage?: number) {
|
getReadingLists(includePromoted: boolean = true, sortByLastModified: boolean = false, pageNum?: number, itemsPerPage?: number) {
|
||||||
let params = new HttpParams();
|
let params = new HttpParams();
|
||||||
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
|
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
|
||||||
|
|
||||||
return this.httpClient.post<PaginatedResult<ReadingList[]>>(this.baseUrl + 'readinglist/lists?includePromoted=' + includePromoted, {}, {observe: 'response', params}).pipe(
|
return this.httpClient.post<PaginatedResult<ReadingList[]>>(this.baseUrl + 'readinglist/lists?includePromoted=' + includePromoted
|
||||||
|
+ '&sortByLastModified=' + sortByLastModified, {}, {observe: 'response', params}).pipe(
|
||||||
map((response: any) => {
|
map((response: any) => {
|
||||||
return this.utilityService.createPaginatedResult(response, new PaginatedResult<ReadingList[]>());
|
return this.utilityService.createPaginatedResult(response, new PaginatedResult<ReadingList[]>());
|
||||||
})
|
})
|
||||||
|
@ -67,10 +67,6 @@ export class SeriesService {
|
|||||||
return this.httpClient.get<Volume[]>(this.baseUrl + 'series/volumes?seriesId=' + seriesId);
|
return this.httpClient.get<Volume[]>(this.baseUrl + 'series/volumes?seriesId=' + seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getVolume(volumeId: number) {
|
|
||||||
return this.httpClient.get<Volume>(this.baseUrl + 'series/volume?volumeId=' + volumeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getChapter(chapterId: number) {
|
getChapter(chapterId: number) {
|
||||||
return this.httpClient.get<Chapter>(this.baseUrl + 'series/chapter?chapterId=' + chapterId);
|
return this.httpClient.get<Chapter>(this.baseUrl + 'series/chapter?chapterId=' + chapterId);
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,10 @@ export class SettingsService {
|
|||||||
return this.http.get<ServerSettings>(this.baseUrl + 'settings');
|
return this.http.get<ServerSettings>(this.baseUrl + 'settings');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBaseUrl() {
|
||||||
|
return this.http.get<string>(this.baseUrl + 'settings/base-url', TextResonse);
|
||||||
|
}
|
||||||
|
|
||||||
updateServerSettings(model: ServerSettings) {
|
updateServerSettings(model: ServerSettings) {
|
||||||
return this.http.post<ServerSettings>(this.baseUrl + 'settings', model);
|
return this.http.post<ServerSettings>(this.baseUrl + 'settings', model);
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,10 @@ and modified
|
|||||||
export class TimeAgoPipe implements PipeTransform, OnDestroy {
|
export class TimeAgoPipe implements PipeTransform, OnDestroy {
|
||||||
|
|
||||||
private timer: number | null = null;
|
private timer: number | null = null;
|
||||||
constructor(private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone) {}
|
constructor(private readonly changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone) {}
|
||||||
transform(value: string) {
|
transform(value: string) {
|
||||||
this.removeTimer();
|
this.removeTimer();
|
||||||
const d = new Date(value);
|
const d = new Date(value);
|
||||||
console.log('date: ', d);
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const seconds = Math.round(Math.abs((now.getTime() - d.getTime()) / 1000));
|
const seconds = Math.round(Math.abs((now.getTime() - d.getTime()) / 1000));
|
||||||
const timeToUpdate = (Number.isNaN(seconds)) ? 1000 : this.getSecondsUntilUpdate(seconds) * 1000;
|
const timeToUpdate = (Number.isNaN(seconds)) ? 1000 : this.getSecondsUntilUpdate(seconds) * 1000;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<div class="d-flex flex-row g-0 mb-2">
|
<div class="d-flex flex-row g-0 mb-2">
|
||||||
<div class="pe-2">
|
<div class="pe-2">
|
||||||
<app-image width="106px" maxHeight="125px" class="img-top me-3" [imageUrl]="imageService.getChapterCoverImage(item.chapterId)"></app-image>
|
<app-image width="106px" maxHeight="125px" class="img-top me-3" [imageUrl]="imageService.getChapterCoverImage(item.chapterId)"></app-image>
|
||||||
<div class="not-read-badge" *ngIf="item.pagesRead === 0 && item.pagesTotal > 0"></div>
|
<ng-container *ngIf="item.pagesRead === 0 && item.pagesTotal > 0">
|
||||||
<div class="progress-banner" *ngIf="item.pagesRead < item.pagesTotal && item.pagesTotal > 0 && item.pagesRead !== item.pagesTotal">
|
<div class="not-read-badge" ></div>
|
||||||
<p><ngb-progressbar type="primary" height="5px" [value]="item.pagesRead" [max]="item.pagesTotal"></ngb-progressbar></p>
|
</ng-container>
|
||||||
</div>
|
<div class="progress-banner" *ngIf="item.pagesRead < item.pagesTotal && item.pagesTotal > 0 && item.pagesRead !== item.pagesTotal">
|
||||||
|
<p><ngb-progressbar type="primary" height="5px" [value]="item.pagesRead" [max]="item.pagesTotal"></ngb-progressbar></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
$image-height: 125px;
|
||||||
|
|
||||||
.progress-banner {
|
.progress-banner {
|
||||||
height: 5px;
|
height: 5px;
|
||||||
|
|
||||||
@ -13,10 +15,20 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.not-read-badge {
|
.badge-container {
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
height: $image-height;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
width: 106px;
|
||||||
left: 108px;
|
}
|
||||||
|
|
||||||
|
.not-read-badge {
|
||||||
|
position: relative;
|
||||||
|
top: -125px;
|
||||||
|
left: 78px;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<app-card-actionables [actions]="globalActions" (actionHandler)="performGlobalAction($event)"></app-card-actionables>
|
<app-card-actionables [actions]="globalActions" (actionHandler)="performGlobalAction($event)"></app-card-actionables>
|
||||||
<span>Reading Lists</span>
|
<span>Reading Lists</span>
|
||||||
</h2>
|
</h2>
|
||||||
<h6 subtitle *ngIf="pagination">{{pagination.totalItems | number}} Items</h6>
|
<h6 subtitle class="subtitle-with-actionables" *ngIf="pagination">{{pagination.totalItems | number}} Items</h6>
|
||||||
</app-side-nav-companion-bar>
|
</app-side-nav-companion-bar>
|
||||||
|
|
||||||
<app-card-detail-layout
|
<app-card-detail-layout
|
||||||
|
@ -97,7 +97,7 @@ export class ReadingListsComponent implements OnInit {
|
|||||||
this.loadingLists = true;
|
this.loadingLists = true;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
|
||||||
this.readingListService.getReadingLists(true).pipe(take(1)).subscribe((readingLists: PaginatedResult<ReadingList[]>) => {
|
this.readingListService.getReadingLists(true, false).pipe(take(1)).subscribe((readingLists: PaginatedResult<ReadingList[]>) => {
|
||||||
this.lists = readingLists.result;
|
this.lists = readingLists.result;
|
||||||
this.pagination = readingLists.pagination;
|
this.pagination = readingLists.pagination;
|
||||||
this.jumpbarKeys = this.jumpbarService.getJumpKeys(readingLists.result, (rl: ReadingList) => rl.title);
|
this.jumpbarKeys = this.jumpbarService.getJumpKeys(readingLists.result, (rl: ReadingList) => rl.title);
|
||||||
|
@ -73,7 +73,7 @@ export class AddToListModalComponent implements OnInit, AfterViewInit {
|
|||||||
this.listForm.addControl('filterQuery', new FormControl('', []));
|
this.listForm.addControl('filterQuery', new FormControl('', []));
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.readingListService.getReadingLists(false).subscribe(lists => {
|
this.readingListService.getReadingLists(false, true).subscribe(lists => {
|
||||||
this.lists = lists.result;
|
this.lists = lists.result;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
|
@ -55,6 +55,8 @@ export class EditReadingListModalComponent implements OnInit, OnDestroy {
|
|||||||
endingYear: new FormControl(this.readingList.endingYear, { nonNullable: true, validators: [Validators.min(1000)] }),
|
endingYear: new FormControl(this.readingList.endingYear, { nonNullable: true, validators: [Validators.min(1000)] }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.coverImageLocked = this.readingList.coverImageLocked;
|
||||||
|
|
||||||
this.reviewGroup.get('title')?.valueChanges.pipe(
|
this.reviewGroup.get('title')?.valueChanges.pipe(
|
||||||
debounceTime(100),
|
debounceTime(100),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
|
@ -107,7 +107,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settingsService.getServerSettings().subscribe(settings => this.baseUrl = settings.baseUrl);
|
this.settingsService.getBaseUrl().subscribe(url => this.baseUrl = url);
|
||||||
|
|
||||||
this.settingsService.getOpdsEnabled().subscribe(res => {
|
this.settingsService.getOpdsEnabled().subscribe(res => {
|
||||||
this.opdsEnabled = res;
|
this.opdsEnabled = res;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#000000",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#000000",
|
||||||
"display": "standalone"
|
"display": "standalone"
|
||||||
}
|
}
|
||||||
|
28
openapi.json
28
openapi.json
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.1.43"
|
"version": "0.7.1.44"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
@ -5007,6 +5007,15 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true
|
"default": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sortByLastModified",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by last modified (most recent first) or by title (alphabetical)",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -7369,19 +7378,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/Server/restart": {
|
|
||||||
"post": {
|
|
||||||
"tags": [
|
|
||||||
"Server"
|
|
||||||
],
|
|
||||||
"summary": "Attempts to Restart the server. Does not work, will shutdown the instance.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Success"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/Server/clear-cache": {
|
"/api/Server/clear-cache": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -15172,6 +15168,10 @@
|
|||||||
},
|
},
|
||||||
"ageRestriction": {
|
"ageRestriction": {
|
||||||
"$ref": "#/components/schemas/AgeRestrictionDto"
|
"$ref": "#/components/schemas/AgeRestrictionDto"
|
||||||
|
},
|
||||||
|
"kavitaVersion": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user