From e8d9a8b3a3696060190b686823c8b034ea59b7a4 Mon Sep 17 00:00:00 2001 From: Joe Milazzo Date: Mon, 29 Jan 2024 13:44:20 -0600 Subject: [PATCH] More Polish (#2665) --- API.Tests/Services/ReaderServiceTests.cs | 31 ++++++++++ API/Controllers/MetadataController.cs | 42 ++++++++------ API/Controllers/OPDSController.cs | 2 +- API/Controllers/PanelsController.cs | 2 +- API/DTOs/OPDS/FeedLink.cs | 2 +- API/DTOs/VolumeDto.cs | 5 ++ .../ExternalSeriesMetadataRepository.cs | 57 ++++++++++--------- API/Data/Repositories/SeriesRepository.cs | 37 +++++++++--- API/Entities/Metadata/ExternalReview.cs | 2 +- API/Helpers/AutoMapperProfiles.cs | 3 +- API/Services/Plus/ExternalMetadataService.cs | 28 ++++----- API/Services/Tasks/Scanner/ProcessSeries.cs | 13 +++-- .../series-detail/series-detail.component.ts | 10 ---- openapi.json | 8 ++- 14 files changed, 151 insertions(+), 91 deletions(-) diff --git a/API.Tests/Services/ReaderServiceTests.cs b/API.Tests/Services/ReaderServiceTests.cs index 322ae4d9d..6f9d71126 100644 --- a/API.Tests/Services/ReaderServiceTests.cs +++ b/API.Tests/Services/ReaderServiceTests.cs @@ -703,6 +703,37 @@ public class ReaderServiceTests Assert.Equal(-1, nextChapter); } + // This is commented out because, while valid, I can't solve how to make this pass (https://github.com/Kareadita/Kavita/issues/2099) + // [Fact] + // public async Task GetNextChapterIdAsync_ShouldFindNoNextChapterFromLastChapter_NoSpecials_FirstIsVolume() + // { + // await ResetDb(); + // + // var series = new SeriesBuilder("Test") + // .WithVolume(new VolumeBuilder("0") + // .WithMinNumber(0) + // .WithChapter(new ChapterBuilder("1").Build()) + // .WithChapter(new ChapterBuilder("2").Build()) + // .Build()) + // .WithVolume(new VolumeBuilder("1") + // .WithMinNumber(1) + // .WithChapter(new ChapterBuilder("0").Build()) + // .Build()) + // .Build(); + // series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); + // + // _context.Series.Add(series); + // _context.AppUser.Add(new AppUser() + // { + // UserName = "majora2007" + // }); + // + // await _context.SaveChangesAsync(); + // + // var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 3, 1); + // Assert.Equal(-1, nextChapter); + // } + // This is commented out because, while valid, I can't solve how to make this pass // [Fact] // public async Task GetNextChapterIdAsync_ShouldFindNoNextChapterFromLastChapter_WithSpecials() diff --git a/API/Controllers/MetadataController.cs b/API/Controllers/MetadataController.cs index ed9560464..ff84d3971 100644 --- a/API/Controllers/MetadataController.cs +++ b/API/Controllers/MetadataController.cs @@ -5,11 +5,14 @@ using System.Linq; using System.Threading.Tasks; using API.Constants; using API.Data; +using API.Data.Misc; +using API.Data.Repositories; using API.DTOs; using API.DTOs.Filtering; using API.DTOs.Metadata; using API.DTOs.Recommendation; using API.DTOs.SeriesDetail; +using API.Entities; using API.Entities.Enums; using API.Extensions; using API.Services; @@ -193,7 +196,6 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc /// /// [HttpGet("series-detail-plus")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = ["seriesId"])] public async Task> GetKavitaPlusSeriesDetailData(int seriesId) { if (!await licenseService.HasActiveLicense()) @@ -203,6 +205,7 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc var user = await unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId()); if (user == null) return Unauthorized(); + var userReviews = (await unitOfWork.UserRepository.GetUserRatingDtosForSeriesAsync(seriesId, user.Id)) .Where(r => !string.IsNullOrEmpty(r.Body)) .OrderByDescending(review => review.Username.Equals(user.UserName) ? 1 : 0) @@ -213,28 +216,35 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc if (results.HasValue) { var cachedResult = results.Value; - userReviews.AddRange(cachedResult.Reviews); - cachedResult.Reviews = ReviewService.SelectSpectrumOfReviews(userReviews); - if (!await unitOfWork.UserRepository.IsUserAdminAsync(user)) - { - cachedResult.Recommendations.ExternalSeries = new List(); - } - + await PrepareSeriesDetail(userReviews, cachedResult, user); return cachedResult; } var ret = await metadataService.GetSeriesDetail(user.Id, seriesId); if (ret == null) return Ok(null); - userReviews.AddRange(ret.Reviews); - ret.Reviews = ReviewService.SelectSpectrumOfReviews(userReviews); - await _cacheProvider.SetAsync(cacheKey, ret, TimeSpan.FromHours(24)); + await _cacheProvider.SetAsync(cacheKey, ret, TimeSpan.FromHours(48)); - if (!await unitOfWork.UserRepository.IsUserAdminAsync(user)) - { - ret.Recommendations.ExternalSeries = new List(); - } + // For some reason if we don't use a different instance, the cache keeps changes made below + var newCacheResult = (await _cacheProvider.GetAsync(cacheKey)).Value; + await PrepareSeriesDetail(userReviews, newCacheResult, user); - return Ok(ret); + return Ok(newCacheResult); } + + private async Task PrepareSeriesDetail(List userReviews, SeriesDetailPlusDto ret, AppUser user) + { + var isAdmin = User.IsInRole(PolicyConstants.AdminRole); + userReviews.AddRange(ReviewService.SelectSpectrumOfReviews(ret.Reviews.ToList())); + ret.Reviews = userReviews; + + if (!isAdmin) + { + // Re-obtain owned series and take into account age restriction + ret.Recommendations.OwnedSeries = + await unitOfWork.SeriesRepository.GetSeriesDtoByIdsAsync( + ret.Recommendations.OwnedSeries.Select(s => s.Id), user); + ret.Recommendations.ExternalSeries = new List(); + } + } } diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index f112905ea..c80b425e7 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -1250,7 +1250,7 @@ public class OpdsController : BaseApiController if (progress != null) { link.LastRead = progress.PageNum; - link.LastReadDate = progress.LastModifiedUtc; + link.LastReadDate = progress.LastModifiedUtc.ToString("o"); // Adhere to ISO 8601 } link.IsPageStream = true; return link; diff --git a/API/Controllers/PanelsController.cs b/API/Controllers/PanelsController.cs index 2008b0c8d..c53b68f86 100644 --- a/API/Controllers/PanelsController.cs +++ b/API/Controllers/PanelsController.cs @@ -57,7 +57,7 @@ public class PanelsController : BaseApiController PageNum = 0, ChapterId = chapterId, VolumeId = 0, - SeriesId = 0 + SeriesId = 0, }); return Ok(progress); } diff --git a/API/DTOs/OPDS/FeedLink.cs b/API/DTOs/OPDS/FeedLink.cs index 2a9053f16..cff3b6736 100644 --- a/API/DTOs/OPDS/FeedLink.cs +++ b/API/DTOs/OPDS/FeedLink.cs @@ -39,7 +39,7 @@ public class FeedLink /// /// Attribute MUST conform Atom's Date construct [XmlAttribute("lastReadDate", Namespace = "http://vaemendis.net/opds-pse/ns")] - public DateTime LastReadDate { get; set; } + public string LastReadDate { get; set; } public bool ShouldSerializeLastReadDate() { diff --git a/API/DTOs/VolumeDto.cs b/API/DTOs/VolumeDto.cs index 1e9a308dc..4820f4d95 100644 --- a/API/DTOs/VolumeDto.cs +++ b/API/DTOs/VolumeDto.cs @@ -15,6 +15,11 @@ public class VolumeDto : IHasReadTimeEstimate public float MaxNumber { get; set; } /// public string Name { get; set; } = default!; + /// + /// This will map to MinNumber. Number was removed in v0.7.13.8/v0.7.14 + /// + [Obsolete("Use MinNumber")] + public float Number { get; set; } public int Pages { get; set; } public int PagesRead { get; set; } public DateTime LastModifiedUtc { get; set; } diff --git a/API/Data/Repositories/ExternalSeriesMetadataRepository.cs b/API/Data/Repositories/ExternalSeriesMetadataRepository.cs index 160c1f24f..bb2ad6b5f 100644 --- a/API/Data/Repositories/ExternalSeriesMetadataRepository.cs +++ b/API/Data/Repositories/ExternalSeriesMetadataRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.Constants; @@ -26,6 +27,7 @@ public interface IExternalSeriesMetadataRepository void Remove(IEnumerable? ratings); void Remove(IEnumerable? recommendations); Task GetExternalSeriesMetadata(int seriesId, int limit = 25); + Task ExternalSeriesMetadataNeedsRefresh(int seriesId, DateTime expireTime); Task GetSeriesDetailPlusDto(int seriesId, int libraryId, AppUser user); Task LinkRecommendationsToSeries(Series series); } @@ -86,24 +88,22 @@ public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepositor return _context.ExternalSeriesMetadata .Where(s => s.SeriesId == seriesId) .Include(s => s.ExternalReviews.Take(limit)) - .Include(s => s.ExternalRatings.Take(limit)) - .Include(s => s.ExternalRecommendations.Take(limit)) + .Include(s => s.ExternalRatings.OrderBy(r => r.AverageScore).Take(limit)) + .Include(s => s.ExternalRecommendations.OrderBy(r => r.Id).Take(limit)) .AsSplitQuery() .FirstOrDefaultAsync(); } + public async Task ExternalSeriesMetadataNeedsRefresh(int seriesId, DateTime expireTime) + { + var row = await _context.ExternalSeriesMetadata + .Where(s => s.SeriesId == seriesId) + .FirstOrDefaultAsync(); + return row == null || row.LastUpdatedUtc <= expireTime; + } + public async Task GetSeriesDetailPlusDto(int seriesId, int libraryId, AppUser user) { - var canSeeExternalSeries = user is { AgeRestriction: AgeRating.NotApplicable } && - await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole); - - var allowedLibraries = await _context.Library - .Where(library => library.AppUsers.Any(x => x.Id == user.Id)) - .Select(l => l.Id) - .ToListAsync(); - - var userRating = await _context.AppUser.GetUserAgeRestriction(user.Id); - var seriesDetailDto = await _context.ExternalSeriesMetadata .Where(m => m.SeriesId == seriesId) .Include(m => m.ExternalRatings) @@ -116,29 +116,30 @@ public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepositor return null; // or handle the case when seriesDetailDto is not found } - var externalSeriesRecommendations = new List(); - if (!canSeeExternalSeries) - { - externalSeriesRecommendations = seriesDetailDto.ExternalRecommendations - .Where(r => r.SeriesId is null or 0) - .Select(r => _mapper.Map(r)) - .ToList(); - } + var externalSeriesRecommendations = seriesDetailDto.ExternalRecommendations + .Where(r => r.SeriesId == null) + .Select(r => _mapper.Map(r)) + .ToList(); + var ownedIds = seriesDetailDto.ExternalRecommendations + .Where(r => r.SeriesId != null) + .Select(r => r.SeriesId) + .ToList(); - var ownedSeriesRecommendations = await _context.ExternalRecommendation - .Where(r => r.SeriesId > 0 && allowedLibraries.Contains(r.Series.LibraryId)) - .Join(_context.Series, r => r.SeriesId, s => s.Id, (recommendation, series) => series) - .RestrictAgainstAgeRestriction(userRating) + var ownedSeriesRecommendations = await _context.Series + .Where(s => ownedIds.Contains(s.Id)) .OrderBy(s => s.SortName.ToLower()) .ProjectTo(_mapper.ConfigurationProvider) - .AsNoTracking() .ToListAsync(); var seriesDetailPlusDto = new SeriesDetailPlusDto() { - Ratings = seriesDetailDto.ExternalRatings.Select(r => _mapper.Map(r)), - Reviews = seriesDetailDto.ExternalReviews.OrderByDescending(r => r.Score) + Ratings = seriesDetailDto.ExternalRatings + .DefaultIfEmpty() + .Select(r => _mapper.Map(r)), + Reviews = seriesDetailDto.ExternalReviews + .DefaultIfEmpty() + .OrderByDescending(r => r.Score) .Select(r => { var ret = _mapper.Map(r); diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 78588a607..f84719e55 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using API.Constants; -using API.Data.ManualMigrations; using API.Data.Misc; using API.Data.Scanner; using API.DTOs; @@ -14,7 +12,6 @@ using API.DTOs.Dashboard; using API.DTOs.Filtering; using API.DTOs.Filtering.v2; using API.DTOs.Metadata; -using API.DTOs.Reader; using API.DTOs.ReadingLists; using API.DTOs.Search; using API.DTOs.SeriesDetail; @@ -34,7 +31,6 @@ using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; -using SQLite; namespace API.Data.Repositories; @@ -95,6 +91,7 @@ public interface ISeriesRepository Task> GetSeriesForLibraryIdAsync(int libraryId, SeriesIncludes includes = SeriesIncludes.None); Task GetSeriesDtoByIdAsync(int seriesId, int userId); Task GetSeriesByIdAsync(int seriesId, SeriesIncludes includes = SeriesIncludes.Volumes | SeriesIncludes.Metadata); + Task> GetSeriesDtoByIdsAsync(IEnumerable seriesIds, AppUser user); Task> GetSeriesByIdsAsync(IList seriesIds); Task GetChapterIdsForSeriesAsync(IList seriesIds); Task>> GetChapterIdWithSeriesIdForSeriesAsync(int[] seriesIds); @@ -569,6 +566,26 @@ public class SeriesRepository : ISeriesRepository .ToListAsync(); } + public async Task> GetSeriesDtoByIdsAsync(IEnumerable seriesIds, AppUser user) + { + var allowedLibraries = await _context.Library + .Where(library => library.AppUsers.Any(x => x.Id == user.Id)) + .Select(l => l.Id) + .ToListAsync(); + var restriction = new AgeRestriction() + { + AgeRating = user.AgeRestriction, + IncludeUnknowns = user.AgeRestrictionIncludeUnknowns + }; + return await _context.Series + .Include(s => s.Metadata) + .Where(s => seriesIds.Contains(s.Id) && allowedLibraries.Contains(s.LibraryId)) + .RestrictAgainstAgeRestriction(restriction) + .AsSplitQuery() + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(); + } + public async Task GetChapterIdsForSeriesAsync(IList seriesIds) { var volumes = await _context.Volume @@ -1889,22 +1906,25 @@ public class SeriesRepository : ISeriesRepository } /// - /// Uses multiple names to find a match against a series then ensures the user has appropriate access to it. If not, returns null. + /// Uses multiple names to find a match against a series. If not, returns null. /// + /// This does not restrict to the user at all. That is handled at the API level. /// /// /// public async Task GetSeriesDtoByNamesAndMetadataIdsForUser(int userId, IEnumerable names, LibraryType libraryType, string aniListUrl, string malUrl) { - var userRating = await _context.AppUser.GetUserAgeRestriction(userId); - var libraryIds = await _context.Library.GetUserLibrariesByType(userId, libraryType).ToListAsync(); + var libraryIds = await _context.Library + .Where(lib => lib.Type == libraryType) + .Select(l => l.Id) + .ToListAsync(); + var normalizedNames = names.Select(n => n.ToNormalized()).ToList(); SeriesDto? result = null; if (!string.IsNullOrEmpty(aniListUrl) || !string.IsNullOrEmpty(malUrl)) { // TODO: I can likely work AniList and MalIds from ExternalSeriesMetadata in here result = await _context.Series - .RestrictAgainstAgeRestriction(userRating) .Where(s => !string.IsNullOrEmpty(s.Metadata.WebLinks)) .Where(s => libraryIds.Contains(s.Library.Id)) .WhereIf(!string.IsNullOrEmpty(aniListUrl), s => s.Metadata.WebLinks.Contains(aniListUrl)) @@ -1917,7 +1937,6 @@ public class SeriesRepository : ISeriesRepository if (result != null) return result; return await _context.Series - .RestrictAgainstAgeRestriction(userRating) .Where(s => normalizedNames.Contains(s.NormalizedName) || normalizedNames.Contains(s.NormalizedLocalizedName)) .Where(s => libraryIds.Contains(s.Library.Id)) diff --git a/API/Entities/Metadata/ExternalReview.cs b/API/Entities/Metadata/ExternalReview.cs index 605af6b29..6304d98ad 100644 --- a/API/Entities/Metadata/ExternalReview.cs +++ b/API/Entities/Metadata/ExternalReview.cs @@ -25,7 +25,6 @@ public class ExternalReview /// Reviewer's username /// public string Username { get; set; } - /// /// An Optional Rating coming from the Review /// @@ -36,6 +35,7 @@ public class ExternalReview public int Score { get; set; } public int TotalVotes { get; set; } + public int SeriesId { get; set; } // Relationships diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs index 1c7f3477f..3f96fd344 100644 --- a/API/Helpers/AutoMapperProfiles.cs +++ b/API/Helpers/AutoMapperProfiles.cs @@ -46,7 +46,8 @@ public class AutoMapperProfiles : Profile .ForMember(dest => dest.ChapterId, opt => opt.MapFrom(src => src.Bookmark.ChapterId)) .ForMember(dest => dest.Series, opt => opt.MapFrom(src => src.Series)); CreateMap(); - CreateMap(); + CreateMap() + .ForMember(dest => dest.Number, opt => opt.MapFrom(src => src.MinNumber)); CreateMap(); CreateMap(); CreateMap(); diff --git a/API/Services/Plus/ExternalMetadataService.cs b/API/Services/Plus/ExternalMetadataService.cs index 60f945741..424d11cf8 100644 --- a/API/Services/Plus/ExternalMetadataService.cs +++ b/API/Services/Plus/ExternalMetadataService.cs @@ -101,14 +101,14 @@ public class ExternalMetadataService : IExternalMetadataService var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId); if (user == null) return new SeriesDetailPlusDto(); - // Let's try to get SeriesDetailPlusDto from the local DB. - var externalSeriesMetadata = await GetExternalSeriesMetadataForSeries(seriesId, series); - var needsRefresh = externalSeriesMetadata.LastUpdatedUtc <= DateTime.UtcNow.Subtract(_externalSeriesMetadataCache); + var needsRefresh = + await _unitOfWork.ExternalSeriesMetadataRepository.ExternalSeriesMetadataNeedsRefresh(seriesId, + DateTime.UtcNow.Subtract(_externalSeriesMetadataCache)); if (!needsRefresh) { // Convert into DTOs and return - return await SerializeExternalSeriesDetail(seriesId, series.LibraryId, user); + return await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesDetailPlusDto(seriesId, series.LibraryId, user); } try @@ -127,6 +127,7 @@ public class ExternalMetadataService : IExternalMetadataService // Clear out existing results + var externalSeriesMetadata = await GetExternalSeriesMetadataForSeries(seriesId, series); _unitOfWork.ExternalSeriesMetadataRepository.Remove(externalSeriesMetadata.ExternalReviews); _unitOfWork.ExternalSeriesMetadataRepository.Remove(externalSeriesMetadata.ExternalRatings); _unitOfWork.ExternalSeriesMetadataRepository.Remove(externalSeriesMetadata.ExternalRecommendations); @@ -150,10 +151,13 @@ public class ExternalMetadataService : IExternalMetadataService externalSeriesMetadata.ExternalRecommendations ??= new List(); var recs = await ProcessRecommendations(series, user, result.Recommendations, externalSeriesMetadata); - externalSeriesMetadata.LastUpdatedUtc = DateTime.UtcNow; - externalSeriesMetadata.AverageExternalRating = (int) externalSeriesMetadata.ExternalRatings + var extRatings = externalSeriesMetadata.ExternalRatings .Where(r => r.AverageScore > 0) - .Average(r => r.AverageScore); + .ToList(); + + externalSeriesMetadata.LastUpdatedUtc = DateTime.UtcNow; + externalSeriesMetadata.AverageExternalRating = extRatings.Count != 0 ? (int) extRatings + .Average(r => r.AverageScore) : 0; if (result.MalId.HasValue) externalSeriesMetadata.MalId = result.MalId.Value; if (result.AniListId.HasValue) externalSeriesMetadata.AniListId = result.AniListId.Value; @@ -164,11 +168,7 @@ public class ExternalMetadataService : IExternalMetadataService { Recommendations = recs, Ratings = result.Ratings, - Reviews = result.Reviews.Select(r => - { - r.IsExternal = true; - return r; - }) + Reviews = externalSeriesMetadata.ExternalReviews.Select(r => _mapper.Map(r)) }; } catch (FlurlHttpException ex) @@ -186,10 +186,6 @@ public class ExternalMetadataService : IExternalMetadataService return null; } - private async Task SerializeExternalSeriesDetail(int seriesId, int libraryId, AppUser user) - { - return await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesDetailPlusDto(seriesId, libraryId, user); - } private async Task GetExternalSeriesMetadataForSeries(int seriesId, Series series) { diff --git a/API/Services/Tasks/Scanner/ProcessSeries.cs b/API/Services/Tasks/Scanner/ProcessSeries.cs index d9ff92282..d24deccad 100644 --- a/API/Services/Tasks/Scanner/ProcessSeries.cs +++ b/API/Services/Tasks/Scanner/ProcessSeries.cs @@ -127,13 +127,14 @@ public class ProcessSeries : IProcessSeries seriesCollisions = seriesCollisions.Where(collision => collision.Name != firstInfo.Series || collision.LocalizedName != firstInfo.LocalizedSeries).ToList(); - if (seriesCollisions.Any()) + if (seriesCollisions.Count > 1) { - var tableRows = seriesCollisions.Select(collision => - $"Name: {firstInfo.Series}Name: {collision.Name}" + - $"Localized: {firstInfo.LocalizedSeries}Localized: {collision.LocalizedName}" + - $"Filename: {Parser.Parser.NormalizePath(_directoryService.FileSystem.FileInfo.New(firstInfo.FullFilePath).Directory?.ToString())}Filename: {Parser.Parser.NormalizePath(collision.FolderPath)}" - ); + var firstCollision = seriesCollisions[0]; + var secondCollision = seriesCollisions[1]; + + var tableRows = $"Name: {firstCollision.Name}Name: {secondCollision.Name}" + + $"Localized: {firstCollision.LocalizedName}Localized: {secondCollision.LocalizedName}" + + $"Filename: {Parser.Parser.NormalizePath(firstCollision.FolderPath)}Filename: {Parser.Parser.NormalizePath(secondCollision.FolderPath)}"; var htmlTable = $"{string.Join(string.Empty, tableRows)}
Series 1Series 2
"; diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts index 2d27cad1a..b740928e3 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts @@ -704,22 +704,12 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { this.ratings = [...data.ratings]; // Recommendations - data.recommendations.ownedSeries.map(r => { - this.seriesService.getMetadata(r.id).subscribe(m => r.summary = m.summary); - }); this.combinedRecs = [...data.recommendations.ownedSeries, ...data.recommendations.externalSeries]; this.hasRecommendations = this.combinedRecs.length > 0; this.cdRef.markForCheck(); }); } - loadReviews() { - this.seriesService.getReviews(this.seriesId).subscribe(reviews => { - this.reviews = [...reviews]; - this.cdRef.markForCheck(); - }); - } - setContinuePoint() { this.readerService.hasSeriesProgress(this.seriesId).subscribe(hasProgress => { diff --git a/openapi.json b/openapi.json index 1d779afe5..5e743d735 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.7.13.9" + "version": "0.7.13.10" }, "servers": [ { @@ -20337,6 +20337,12 @@ "type": "string", "nullable": true }, + "number": { + "type": "number", + "description": "This will map to MinNumber. Number was removed in v0.7.13.8", + "format": "float", + "deprecated": true + }, "pages": { "type": "integer", "format": "int32"