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
|
||||
{
|
||||
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]
|
||||
public async Task GetWordCount_PassByRef()
|
||||
@ -100,6 +100,6 @@ public class EpubBenchmark
|
||||
|
||||
|
||||
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
|
||||
|
||||
public void UpdateChapterFromComicInfo_()
|
||||
{
|
||||
// TODO: Do this
|
||||
var file = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ScannerService/Library/Manga/Hajime no Ippo/Hajime no Ippo Chapter 1.cbz");
|
||||
// Chapter and ComicInfo
|
||||
var chapter = new ChapterBuilder("1")
|
||||
.WithId(0)
|
||||
.WithFile(new MangaFileBuilder(file, MangaFormat.Archive).Build())
|
||||
.Build();
|
||||
|
||||
var ps = new ProcessSeries(Substitute.For<IUnitOfWork>(), Substitute.For<ILogger<ProcessSeries>>(),
|
||||
Substitute.For<IEventHub>(), Substitute.For<IDirectoryService>()
|
||||
, Substitute.For<ICacheHelper>(), Substitute.For<IReadingItemService>(), Substitute.For<IFileService>(),
|
||||
Substitute.For<IMetadataService>(),
|
||||
Substitute.For<IWordCountAnalyzerService>(),
|
||||
Substitute.For<ICollectionTagService>(), Substitute.For<IReadingListService>());
|
||||
|
||||
ps.UpdateChapterFromComicInfo(chapter, new ComicInfo()
|
||||
{
|
||||
|
||||
});
|
||||
}
|
||||
// public void UpdateChapterFromComicInfo_()
|
||||
// {
|
||||
// // TODO: Do this
|
||||
// var file = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ScannerService/Library/Manga/Hajime no Ippo/Hajime no Ippo Chapter 1.cbz");
|
||||
// // Chapter and ComicInfo
|
||||
// var chapter = new ChapterBuilder("1")
|
||||
// .WithId(0)
|
||||
// .WithFile(new MangaFileBuilder(file, MangaFormat.Archive).Build())
|
||||
// .Build();
|
||||
//
|
||||
// var ps = new ProcessSeries(Substitute.For<IUnitOfWork>(), Substitute.For<ILogger<ProcessSeries>>(),
|
||||
// Substitute.For<IEventHub>(), Substitute.For<IDirectoryService>()
|
||||
// , Substitute.For<ICacheHelper>(), Substitute.For<IReadingItemService>(), Substitute.For<IFileService>(),
|
||||
// Substitute.For<IMetadataService>(),
|
||||
// Substitute.For<IWordCountAnalyzerService>(),
|
||||
// Substitute.For<ICollectionTagService>(), Substitute.For<IReadingListService>());
|
||||
//
|
||||
// ps.UpdateChapterFromComicInfo(chapter, new ComicInfo()
|
||||
// {
|
||||
//
|
||||
// });
|
||||
// }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -150,7 +150,8 @@ public class AccountController : BaseApiController
|
||||
Token = await _tokenService.CreateToken(user),
|
||||
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
||||
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)
|
||||
@ -213,6 +214,8 @@ public class AccountController : BaseApiController
|
||||
var dto = _mapper.Map<UserDto>(user);
|
||||
dto.Token = await _tokenService.CreateToken(user);
|
||||
dto.RefreshToken = await _tokenService.CreateRefreshToken(user);
|
||||
dto.KavitaVersion = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion))
|
||||
.Value;
|
||||
var pref = await _unitOfWork.UserRepository.GetPreferencesAsync(user.UserName!);
|
||||
if (pref == null) return Ok(dto);
|
||||
|
||||
@ -687,7 +690,8 @@ public class AccountController : BaseApiController
|
||||
Token = await _tokenService.CreateToken(user),
|
||||
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
||||
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),
|
||||
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
||||
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>()
|
||||
{
|
||||
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.Thumbnail, 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}&apiKey={apiKey}")
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -294,7 +294,8 @@ public class OpdsController : BaseApiController
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
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);
|
||||
@ -480,9 +481,9 @@ public class OpdsController : BaseApiController
|
||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
|
||||
$"{prefix}{apiKey}/collections/{collection.Id}"),
|
||||
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,
|
||||
$"{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);
|
||||
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);
|
||||
foreach (var volume in seriesDetail.Volumes)
|
||||
@ -737,8 +738,8 @@ public class OpdsController : BaseApiController
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
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.Thumbnail, 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}&apiKey={apiKey}")
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -752,8 +753,8 @@ public class OpdsController : BaseApiController
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
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.Thumbnail, 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}&apiKey={apiKey}")
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -770,9 +771,9 @@ public class OpdsController : BaseApiController
|
||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
|
||||
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}"),
|
||||
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,
|
||||
$"{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 volume = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, await GetUser(apiKey));
|
||||
|
||||
|
||||
var title = $"{series.Name}";
|
||||
|
||||
if (volume.Chapters.Count == 1)
|
||||
if (volume!.Chapters.Count == 1)
|
||||
{
|
||||
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType);
|
||||
if (volume.Name != "0")
|
||||
@ -823,8 +825,8 @@ public class OpdsController : BaseApiController
|
||||
Format = mangaFile.Format.ToString(),
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/chapter-cover?chapterId={chapterId}"),
|
||||
CreateLink(FeedLinkRelation.Thumbnail, 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}&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
|
||||
accLink,
|
||||
await CreatePageStreamLink(series.LibraryId, seriesId, volumeId, chapterId, mangaFile, apiKey, prefix)
|
||||
|
@ -2,7 +2,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs;
|
||||
using API.Entities.Enums;
|
||||
using API.Services;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -45,6 +47,7 @@ public class PluginController : BaseApiController
|
||||
Token = await _tokenService.CreateToken(user),
|
||||
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
||||
ApiKey = user.ApiKey,
|
||||
KavitaVersion = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion)).Value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -47,13 +47,15 @@ public class ReadingListController : BaseApiController
|
||||
/// </summary>
|
||||
/// <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="sortByLastModified">Sort by last modified (most recent first) or by title (alphabetical)</param>
|
||||
/// <returns></returns>
|
||||
[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 items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, includePromoted,
|
||||
userParams);
|
||||
userParams, sortByLastModified);
|
||||
Response.AddPaginationHeader(items.CurrentPage, items.PageSize, items.TotalCount, items.TotalPages);
|
||||
|
||||
return Ok(items);
|
||||
|
@ -111,7 +111,7 @@ public class SeriesController : BaseApiController
|
||||
}
|
||||
|
||||
[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());
|
||||
return Ok(await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, userId));
|
||||
|
@ -53,19 +53,6 @@ public class ServerController : BaseApiController
|
||||
_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>
|
||||
/// Performs an ad-hoc cleanup of Cache
|
||||
/// </summary>
|
||||
|
@ -45,7 +45,6 @@ public class SettingsController : BaseApiController
|
||||
_libraryWatcher = libraryWatcher;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("base-url")]
|
||||
public async Task<ActionResult<string>> GetBaseUrl()
|
||||
{
|
||||
|
@ -12,4 +12,5 @@ public class UserDto
|
||||
public string? ApiKey { get; init; }
|
||||
public UserPreferencesDto? Preferences { get; set; }
|
||||
public AgeRestrictionDto? AgeRestriction { get; init; }
|
||||
public string KavitaVersion { get; set; }
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ public enum ReadingListIncludes
|
||||
|
||||
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<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(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 query = _context.ReadingList
|
||||
.Where(l => l.AppUserId == userId || (includePromoted && l.Promoted ))
|
||||
.Where(l => l.AgeRating >= userAgeRating)
|
||||
.OrderBy(l => l.LastModified)
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.Where(l => l.AgeRating >= userAgeRating);
|
||||
query = sortByLastModified ? query.OrderByDescending(l => l.LastModified) : query.OrderBy(l => l.NormalizedTitle);
|
||||
|
||||
var finalQuery = query.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.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)
|
||||
|
@ -140,10 +140,8 @@ public class SeriesRepository : ISeriesRepository
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
|
||||
// [GeneratedRegex(@"\d{4}", RegexOptions.Compiled, 50000)]
|
||||
// private static partial Regex YearRegex();
|
||||
private readonly Regex _yearRegex = new Regex(@"\d{4}", RegexOptions.Compiled, Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
private readonly Regex _yearRegex = new Regex(@"\d{4}", RegexOptions.Compiled,
|
||||
Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
|
||||
public SeriesRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ public class SiteThemeRepository : ISiteThemeRepository
|
||||
{
|
||||
var result = await _context.SiteTheme
|
||||
.Where(t => t.IsDefault)
|
||||
.SingleOrDefaultAsync();
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
|
@ -337,7 +337,7 @@ public class UserRepository : IUserRepository
|
||||
return await _context.AppUser
|
||||
.Where(u => u.ApiKey != null && u.ApiKey.Equals(apiKey))
|
||||
.Select(u => u.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
@ -21,7 +22,7 @@ public interface IVolumeRepository
|
||||
Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds);
|
||||
Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId);
|
||||
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>> GetVolumes(int seriesId);
|
||||
Task<Volume?> GetVolumeByIdAsync(int volumeId);
|
||||
@ -119,7 +120,7 @@ public class VolumeRepository : IVolumeRepository
|
||||
/// <param name="volumeId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <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
|
||||
.Where(vol => vol.Id == volumeId)
|
||||
@ -127,7 +128,9 @@ public class VolumeRepository : IVolumeRepository
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.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};
|
||||
await AddVolumeModifiers(userId, volumeList);
|
||||
|
@ -20,8 +20,6 @@ public static class RestrictByAgeExtensions
|
||||
return q.Where(s => s.Metadata.AgeRating != AgeRating.Unknown);
|
||||
}
|
||||
|
||||
//q.WhereIf(!restriction.IncludeUnknowns, s => s.Metadata.AgeRating != AgeRating.Unknown);
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,6 @@ namespace API.Extensions;
|
||||
|
||||
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+(.)",
|
||||
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)
|
||||
{
|
||||
_logger = logger;
|
||||
@ -287,7 +266,6 @@ public class BookService : IBookService
|
||||
|
||||
if (images == null) return;
|
||||
|
||||
|
||||
var parent = images.First().ParentNode;
|
||||
|
||||
foreach (var image in images)
|
||||
|
@ -76,23 +76,14 @@ public class DirectoryService : IDirectoryService
|
||||
public string BookmarkDirectory { get; }
|
||||
public string SiteThemeDirectory { get; }
|
||||
private readonly ILogger<DirectoryService> _logger;
|
||||
private const int RegexTimeoutMs = 5000000;
|
||||
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(
|
||||
@"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle|\.@__thumb",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase,
|
||||
MatchOptions,
|
||||
Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
private static readonly Regex FileCopyAppend = new Regex(@"\(\d+\)",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase,
|
||||
MatchOptions,
|
||||
Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
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)
|
||||
{
|
||||
// TODO: Needs testing
|
||||
// Currently this doesn't work due to non-standard cover image sizes and dimensions
|
||||
var image = Image.Black(320*4, 160*4);
|
||||
|
||||
|
@ -273,7 +273,8 @@ public class ReaderService : IReaderService
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ public interface IReadingListService
|
||||
/// <summary>
|
||||
/// Methods responsible for management of Reading Lists
|
||||
/// </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
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
@ -152,7 +152,7 @@ public class ReadingListService : IReadingListService
|
||||
|
||||
readingList.Summary = dto.Summary;
|
||||
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.CoverImageLocked = dto.CoverImageLocked;
|
||||
|
||||
@ -193,7 +193,7 @@ public class ReadingListService : IReadingListService
|
||||
/// <summary>
|
||||
/// Removes all entries that are fully read from the reading list. This commits
|
||||
/// </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="user">User</param>
|
||||
/// <returns></returns>
|
||||
@ -203,8 +203,9 @@ public class ReadingListService : IReadingListService
|
||||
items = await _unitOfWork.ReadingListRepository.AddReadingProgressModifiers(user.Id, items.ToList());
|
||||
|
||||
// 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
|
||||
{
|
||||
var listItems =
|
||||
@ -218,7 +219,6 @@ public class ReadingListService : IReadingListService
|
||||
await CalculateStartAndEndDates(readingList);
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return true;
|
||||
|
||||
return await _unitOfWork.CommitAsync();
|
||||
}
|
||||
catch
|
||||
@ -313,8 +313,8 @@ public class ReadingListService : IReadingListService
|
||||
_logger.LogError("Tried to calculate release dates for Reading List, but missing Chapter entities");
|
||||
return;
|
||||
}
|
||||
var maxReleaseDate = items.Max(item => item.Chapter.ReleaseDate);
|
||||
var minReleaseDate = items.Min(item => item.Chapter.ReleaseDate);
|
||||
var maxReleaseDate = items.Where(item => item.Chapter != null).Max(item => item.Chapter.ReleaseDate);
|
||||
var minReleaseDate = items.Where(item => item.Chapter != null).Min(item => item.Chapter.ReleaseDate);
|
||||
if (maxReleaseDate != DateTime.MinValue)
|
||||
{
|
||||
readingListWithItems.EndingMonth = maxReleaseDate.Month;
|
||||
|
@ -97,7 +97,7 @@ public class TachiyomiService : ITachiyomiService
|
||||
|
||||
var volumeWithProgress = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(prevChapter.VolumeId, userId);
|
||||
// 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
|
||||
return new ChapterDto()
|
||||
|
@ -2,5 +2,5 @@
|
||||
"TokenKey": "super secret unguessable key",
|
||||
"Port": 5000,
|
||||
"IpAddresses": "",
|
||||
"BaseUrl": "/test/"
|
||||
"BaseUrl": "/"
|
||||
}
|
@ -24,11 +24,12 @@ export class ReadingListService {
|
||||
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();
|
||||
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) => {
|
||||
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);
|
||||
}
|
||||
|
||||
getVolume(volumeId: number) {
|
||||
return this.httpClient.get<Volume>(this.baseUrl + 'series/volume?volumeId=' + volumeId);
|
||||
}
|
||||
|
||||
getChapter(chapterId: number) {
|
||||
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');
|
||||
}
|
||||
|
||||
getBaseUrl() {
|
||||
return this.http.get<string>(this.baseUrl + 'settings/base-url', TextResonse);
|
||||
}
|
||||
|
||||
updateServerSettings(model: ServerSettings) {
|
||||
return this.http.post<ServerSettings>(this.baseUrl + 'settings', model);
|
||||
}
|
||||
|
@ -34,11 +34,10 @@ and modified
|
||||
export class TimeAgoPipe implements PipeTransform, OnDestroy {
|
||||
|
||||
private timer: number | null = null;
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone) {}
|
||||
constructor(private readonly changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone) {}
|
||||
transform(value: string) {
|
||||
this.removeTimer();
|
||||
const d = new Date(value);
|
||||
console.log('date: ', d);
|
||||
const now = new Date();
|
||||
const seconds = Math.round(Math.abs((now.getTime() - d.getTime()) / 1000));
|
||||
const timeToUpdate = (Number.isNaN(seconds)) ? 1000 : this.getSecondsUntilUpdate(seconds) * 1000;
|
||||
|
@ -1,7 +1,9 @@
|
||||
<div class="d-flex flex-row g-0 mb-2">
|
||||
<div class="pe-2">
|
||||
<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="not-read-badge" ></div>
|
||||
</ng-container>
|
||||
<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>
|
||||
|
@ -1,3 +1,5 @@
|
||||
$image-height: 125px;
|
||||
|
||||
.progress-banner {
|
||||
height: 5px;
|
||||
|
||||
@ -13,10 +15,20 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.not-read-badge {
|
||||
.badge-container {
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
height: $image-height;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 108px;
|
||||
width: 106px;
|
||||
}
|
||||
|
||||
.not-read-badge {
|
||||
position: relative;
|
||||
top: -125px;
|
||||
left: 78px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
|
@ -3,7 +3,7 @@
|
||||
<app-card-actionables [actions]="globalActions" (actionHandler)="performGlobalAction($event)"></app-card-actionables>
|
||||
<span>Reading Lists</span>
|
||||
</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-card-detail-layout
|
||||
|
@ -97,7 +97,7 @@ export class ReadingListsComponent implements OnInit {
|
||||
this.loadingLists = true;
|
||||
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.pagination = readingLists.pagination;
|
||||
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.loading = true;
|
||||
this.readingListService.getReadingLists(false).subscribe(lists => {
|
||||
this.readingListService.getReadingLists(false, true).subscribe(lists => {
|
||||
this.lists = lists.result;
|
||||
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)] }),
|
||||
});
|
||||
|
||||
this.coverImageLocked = this.readingList.coverImageLocked;
|
||||
|
||||
this.reviewGroup.get('title')?.valueChanges.pipe(
|
||||
debounceTime(100),
|
||||
distinctUntilChanged(),
|
||||
|
@ -107,7 +107,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||
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.opdsEnabled = res;
|
||||
|
@ -13,7 +13,7 @@
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#000000",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
28
openapi.json
28
openapi.json
@ -7,7 +7,7 @@
|
||||
"name": "GPL-3.0",
|
||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||
},
|
||||
"version": "0.7.1.43"
|
||||
"version": "0.7.1.44"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
@ -5007,6 +5007,15 @@
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortByLastModified",
|
||||
"in": "query",
|
||||
"description": "Sort by last modified (most recent first) or by title (alphabetical)",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"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": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -15172,6 +15168,10 @@
|
||||
},
|
||||
"ageRestriction": {
|
||||
"$ref": "#/components/schemas/AgeRestrictionDto"
|
||||
},
|
||||
"kavitaVersion": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
Loading…
x
Reference in New Issue
Block a user