mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
More Polish (#2665)
This commit is contained in:
parent
47547c23dd
commit
e8d9a8b3a3
@ -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()
|
||||
|
@ -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
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("series-detail-plus")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = ["seriesId"])]
|
||||
public async Task<ActionResult<SeriesDetailPlusDto>> 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<ExternalSeriesDto>();
|
||||
}
|
||||
|
||||
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<ExternalSeriesDto>();
|
||||
}
|
||||
// For some reason if we don't use a different instance, the cache keeps changes made below
|
||||
var newCacheResult = (await _cacheProvider.GetAsync<SeriesDetailPlusDto>(cacheKey)).Value;
|
||||
await PrepareSeriesDetail(userReviews, newCacheResult, user);
|
||||
|
||||
return Ok(ret);
|
||||
return Ok(newCacheResult);
|
||||
|
||||
}
|
||||
|
||||
private async Task PrepareSeriesDetail(List<UserReviewDto> 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<ExternalSeriesDto>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -57,7 +57,7 @@ public class PanelsController : BaseApiController
|
||||
PageNum = 0,
|
||||
ChapterId = chapterId,
|
||||
VolumeId = 0,
|
||||
SeriesId = 0
|
||||
SeriesId = 0,
|
||||
});
|
||||
return Ok(progress);
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public class FeedLink
|
||||
/// </summary>
|
||||
/// <remarks>Attribute MUST conform Atom's Date construct</remarks>
|
||||
[XmlAttribute("lastReadDate", Namespace = "http://vaemendis.net/opds-pse/ns")]
|
||||
public DateTime LastReadDate { get; set; }
|
||||
public string LastReadDate { get; set; }
|
||||
|
||||
public bool ShouldSerializeLastReadDate()
|
||||
{
|
||||
|
@ -15,6 +15,11 @@ public class VolumeDto : IHasReadTimeEstimate
|
||||
public float MaxNumber { get; set; }
|
||||
/// <inheritdoc cref="Volume.Name"/>
|
||||
public string Name { get; set; } = default!;
|
||||
/// <summary>
|
||||
/// This will map to MinNumber. Number was removed in v0.7.13.8/v0.7.14
|
||||
/// </summary>
|
||||
[Obsolete("Use MinNumber")]
|
||||
public float Number { get; set; }
|
||||
public int Pages { get; set; }
|
||||
public int PagesRead { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
|
@ -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<ExternalRating>? ratings);
|
||||
void Remove(IEnumerable<ExternalRecommendation>? recommendations);
|
||||
Task<ExternalSeriesMetadata?> GetExternalSeriesMetadata(int seriesId, int limit = 25);
|
||||
Task<bool> ExternalSeriesMetadataNeedsRefresh(int seriesId, DateTime expireTime);
|
||||
Task<SeriesDetailPlusDto> 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<bool> 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<SeriesDetailPlusDto> 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<ExternalSeriesDto>();
|
||||
if (!canSeeExternalSeries)
|
||||
{
|
||||
externalSeriesRecommendations = seriesDetailDto.ExternalRecommendations
|
||||
.Where(r => r.SeriesId is null or 0)
|
||||
.Select(r => _mapper.Map<ExternalSeriesDto>(r))
|
||||
.ToList();
|
||||
}
|
||||
var externalSeriesRecommendations = seriesDetailDto.ExternalRecommendations
|
||||
.Where(r => r.SeriesId == null)
|
||||
.Select(r => _mapper.Map<ExternalSeriesDto>(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<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
var seriesDetailPlusDto = new SeriesDetailPlusDto()
|
||||
{
|
||||
Ratings = seriesDetailDto.ExternalRatings.Select(r => _mapper.Map<RatingDto>(r)),
|
||||
Reviews = seriesDetailDto.ExternalReviews.OrderByDescending(r => r.Score)
|
||||
Ratings = seriesDetailDto.ExternalRatings
|
||||
.DefaultIfEmpty()
|
||||
.Select(r => _mapper.Map<RatingDto>(r)),
|
||||
Reviews = seriesDetailDto.ExternalReviews
|
||||
.DefaultIfEmpty()
|
||||
.OrderByDescending(r => r.Score)
|
||||
.Select(r =>
|
||||
{
|
||||
var ret = _mapper.Map<UserReviewDto>(r);
|
||||
|
@ -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<IEnumerable<Series>> GetSeriesForLibraryIdAsync(int libraryId, SeriesIncludes includes = SeriesIncludes.None);
|
||||
Task<SeriesDto?> GetSeriesDtoByIdAsync(int seriesId, int userId);
|
||||
Task<Series?> GetSeriesByIdAsync(int seriesId, SeriesIncludes includes = SeriesIncludes.Volumes | SeriesIncludes.Metadata);
|
||||
Task<IList<SeriesDto>> GetSeriesDtoByIdsAsync(IEnumerable<int> seriesIds, AppUser user);
|
||||
Task<IList<Series>> GetSeriesByIdsAsync(IList<int> seriesIds);
|
||||
Task<int[]> GetChapterIdsForSeriesAsync(IList<int> seriesIds);
|
||||
Task<IDictionary<int, IList<int>>> GetChapterIdWithSeriesIdForSeriesAsync(int[] seriesIds);
|
||||
@ -569,6 +566,26 @@ public class SeriesRepository : ISeriesRepository
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<SeriesDto>> GetSeriesDtoByIdsAsync(IEnumerable<int> 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<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<int[]> GetChapterIdsForSeriesAsync(IList<int> seriesIds)
|
||||
{
|
||||
var volumes = await _context.Volume
|
||||
@ -1889,22 +1906,25 @@ public class SeriesRepository : ISeriesRepository
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>This does not restrict to the user at all. That is handled at the API level.</remarks>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="names"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<SeriesDto?> GetSeriesDtoByNamesAndMetadataIdsForUser(int userId, IEnumerable<string> 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))
|
||||
|
@ -25,7 +25,6 @@ public class ExternalReview
|
||||
/// Reviewer's username
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An Optional Rating coming from the Review
|
||||
/// </summary>
|
||||
@ -36,6 +35,7 @@ public class ExternalReview
|
||||
public int Score { get; set; }
|
||||
public int TotalVotes { get; set; }
|
||||
|
||||
|
||||
public int SeriesId { get; set; }
|
||||
|
||||
// Relationships
|
||||
|
@ -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<LibraryDto, Library>();
|
||||
CreateMap<Volume, VolumeDto>();
|
||||
CreateMap<Volume, VolumeDto>()
|
||||
.ForMember(dest => dest.Number, opt => opt.MapFrom(src => src.MinNumber));
|
||||
CreateMap<MangaFile, MangaFileDto>();
|
||||
CreateMap<Chapter, ChapterDto>();
|
||||
CreateMap<Series, SeriesDto>();
|
||||
|
@ -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<ExternalRecommendation>();
|
||||
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<UserReviewDto>(r))
|
||||
};
|
||||
}
|
||||
catch (FlurlHttpException ex)
|
||||
@ -186,10 +186,6 @@ public class ExternalMetadataService : IExternalMetadataService
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<SeriesDetailPlusDto?> SerializeExternalSeriesDetail(int seriesId, int libraryId, AppUser user)
|
||||
{
|
||||
return await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesDetailPlusDto(seriesId, libraryId, user);
|
||||
}
|
||||
|
||||
private async Task<ExternalSeriesMetadata> GetExternalSeriesMetadataForSeries(int seriesId, Series series)
|
||||
{
|
||||
|
@ -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 =>
|
||||
$"<tr><td>Name: {firstInfo.Series}</td><td>Name: {collision.Name}</td></tr>" +
|
||||
$"<tr><td>Localized: {firstInfo.LocalizedSeries}</td><td>Localized: {collision.LocalizedName}</td></tr>" +
|
||||
$"<tr><td>Filename: {Parser.Parser.NormalizePath(_directoryService.FileSystem.FileInfo.New(firstInfo.FullFilePath).Directory?.ToString())}</td><td>Filename: {Parser.Parser.NormalizePath(collision.FolderPath)}</td></tr>"
|
||||
);
|
||||
var firstCollision = seriesCollisions[0];
|
||||
var secondCollision = seriesCollisions[1];
|
||||
|
||||
var tableRows = $"<tr><td>Name: {firstCollision.Name}</td><td>Name: {secondCollision.Name}</td></tr>" +
|
||||
$"<tr><td>Localized: {firstCollision.LocalizedName}</td><td>Localized: {secondCollision.LocalizedName}</td></tr>" +
|
||||
$"<tr><td>Filename: {Parser.Parser.NormalizePath(firstCollision.FolderPath)}</td><td>Filename: {Parser.Parser.NormalizePath(secondCollision.FolderPath)}</td></tr>";
|
||||
|
||||
var htmlTable = $"<table class='table table-striped'><thead><tr><th>Series 1</th><th>Series 2</th></tr></thead><tbody>{string.Join(string.Empty, tableRows)}</tbody></table>";
|
||||
|
||||
|
@ -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 => {
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user