mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
A few reading list bug fixes (#3663)
This commit is contained in:
parent
0785d4afab
commit
a7e1386bad
@ -932,7 +932,8 @@ public class SeriesFilterTests : AbstractDbTest
|
||||
|
||||
var seriesService = new SeriesService(_unitOfWork, Substitute.For<IEventHub>(),
|
||||
Substitute.For<ITaskScheduler>(), Substitute.For<ILogger<SeriesService>>(),
|
||||
Substitute.For<IScrobblingService>(), Substitute.For<ILocalizationService>());
|
||||
Substitute.For<IScrobblingService>(), Substitute.For<ILocalizationService>(),
|
||||
Substitute.For<IReadingListService>());
|
||||
|
||||
// Select 0 Rating
|
||||
var zeroRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2);
|
||||
|
@ -583,6 +583,93 @@ public class ReadingListServiceTests
|
||||
Assert.Equal(AgeRating.G, readingList.AgeRating);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateReadingListAgeRatingForSeries()
|
||||
{
|
||||
await ResetDb();
|
||||
var spiceAndWolf = new SeriesBuilder("Spice and Wolf")
|
||||
.WithMetadata(new SeriesMetadataBuilder().Build())
|
||||
.WithVolumes([
|
||||
new VolumeBuilder("1")
|
||||
.WithChapters([
|
||||
new ChapterBuilder("1").Build(),
|
||||
new ChapterBuilder("2").Build(),
|
||||
]).Build()
|
||||
]).Build();
|
||||
spiceAndWolf.Metadata.AgeRating = AgeRating.Everyone;
|
||||
|
||||
var othersidePicnic = new SeriesBuilder("Otherside Picnic ")
|
||||
.WithMetadata(new SeriesMetadataBuilder().Build())
|
||||
.WithVolumes([
|
||||
new VolumeBuilder("1")
|
||||
.WithChapters([
|
||||
new ChapterBuilder("1").Build(),
|
||||
new ChapterBuilder("2").Build(),
|
||||
]).Build()
|
||||
]).Build();
|
||||
othersidePicnic.Metadata.AgeRating = AgeRating.Everyone;
|
||||
|
||||
_context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "Amelia",
|
||||
ReadingLists = new List<ReadingList>(),
|
||||
Libraries = new List<Library>
|
||||
{
|
||||
new LibraryBuilder("Test Library", LibraryType.LightNovel)
|
||||
.WithSeries(spiceAndWolf)
|
||||
.WithSeries(othersidePicnic)
|
||||
.Build(),
|
||||
},
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("Amelia", AppUserIncludes.ReadingLists);
|
||||
Assert.NotNull(user);
|
||||
|
||||
var myTestReadingList = new ReadingListBuilder("MyReadingList").Build();
|
||||
var mySecondTestReadingList = new ReadingListBuilder("MySecondReadingList").Build();
|
||||
var myThirdTestReadingList = new ReadingListBuilder("MyThirdReadingList").Build();
|
||||
user.ReadingLists = new List<ReadingList>()
|
||||
{
|
||||
myTestReadingList,
|
||||
mySecondTestReadingList,
|
||||
myThirdTestReadingList,
|
||||
};
|
||||
|
||||
|
||||
await _readingListService.AddChaptersToReadingList(spiceAndWolf.Id, new List<int> {1, 2}, myTestReadingList);
|
||||
await _readingListService.AddChaptersToReadingList(othersidePicnic.Id, new List<int> {3, 4}, myTestReadingList);
|
||||
await _readingListService.AddChaptersToReadingList(spiceAndWolf.Id, new List<int> {1, 2}, myThirdTestReadingList);
|
||||
await _readingListService.AddChaptersToReadingList(othersidePicnic.Id, new List<int> {3, 4}, mySecondTestReadingList);
|
||||
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
await _readingListService.CalculateReadingListAgeRating(myTestReadingList);
|
||||
await _readingListService.CalculateReadingListAgeRating(mySecondTestReadingList);
|
||||
Assert.Equal(AgeRating.Everyone, myTestReadingList.AgeRating);
|
||||
Assert.Equal(AgeRating.Everyone, mySecondTestReadingList.AgeRating);
|
||||
Assert.Equal(AgeRating.Everyone, myThirdTestReadingList.AgeRating);
|
||||
|
||||
await _readingListService.UpdateReadingListAgeRatingForSeries(othersidePicnic.Id, AgeRating.Mature);
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
// Reading lists containing Otherside Picnic are updated
|
||||
myTestReadingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(1);
|
||||
Assert.NotNull(myTestReadingList);
|
||||
Assert.Equal(AgeRating.Mature, myTestReadingList.AgeRating);
|
||||
|
||||
mySecondTestReadingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(2);
|
||||
Assert.NotNull(mySecondTestReadingList);
|
||||
Assert.Equal(AgeRating.Mature, mySecondTestReadingList.AgeRating);
|
||||
|
||||
// Unrelated reading list is not updated
|
||||
myThirdTestReadingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(3);
|
||||
Assert.NotNull(myThirdTestReadingList);
|
||||
Assert.Equal(AgeRating.Everyone, myThirdTestReadingList.AgeRating);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CalculateStartAndEndDates
|
||||
|
@ -57,7 +57,7 @@ public class SeriesServiceTests : AbstractDbTest
|
||||
|
||||
_seriesService = new SeriesService(_unitOfWork, Substitute.For<IEventHub>(),
|
||||
Substitute.For<ITaskScheduler>(), Substitute.For<ILogger<SeriesService>>(),
|
||||
Substitute.For<IScrobblingService>(), locService);
|
||||
Substitute.For<IScrobblingService>(), locService, Substitute.For<IReadingListService>());
|
||||
}
|
||||
#region Setup
|
||||
|
||||
|
@ -27,6 +27,7 @@ using AutoMapper;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeTypes;
|
||||
|
||||
namespace API.Controllers;
|
||||
@ -36,6 +37,7 @@ namespace API.Controllers;
|
||||
[AllowAnonymous]
|
||||
public class OpdsController : BaseApiController
|
||||
{
|
||||
private readonly ILogger<OpdsController> _logger;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IDownloadService _downloadService;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
@ -82,7 +84,7 @@ public class OpdsController : BaseApiController
|
||||
IDirectoryService directoryService, ICacheService cacheService,
|
||||
IReaderService readerService, ISeriesService seriesService,
|
||||
IAccountService accountService, ILocalizationService localizationService,
|
||||
IMapper mapper)
|
||||
IMapper mapper, ILogger<OpdsController> logger)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_downloadService = downloadService;
|
||||
@ -93,6 +95,7 @@ public class OpdsController : BaseApiController
|
||||
_accountService = accountService;
|
||||
_localizationService = localizationService;
|
||||
_mapper = mapper;
|
||||
_logger = logger;
|
||||
|
||||
_xmlSerializer = new XmlSerializer(typeof(Feed));
|
||||
_xmlOpenSearchSerializer = new XmlSerializer(typeof(OpenSearchDescription));
|
||||
@ -580,19 +583,31 @@ public class OpdsController : BaseApiController
|
||||
public async Task<IActionResult> GetReadingListItems(int readingListId, string apiKey, [FromQuery] int pageNumber = 0)
|
||||
{
|
||||
var userId = await GetUser(apiKey);
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
|
||||
var userWithLists = await _unitOfWork.UserRepository.GetUserByUsernameAsync(user!.UserName!, AppUserIncludes.ReadingListsWithItems);
|
||||
if (userWithLists == null) return Unauthorized();
|
||||
var readingList = userWithLists.ReadingLists.SingleOrDefault(t => t.Id == readingListId);
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
|
||||
}
|
||||
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var readingLists = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(user.Id, true, GetUserParams(pageNumber), false);
|
||||
if (readingLists == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var readingList = readingLists.FirstOrDefault(rl => rl.Id == readingListId);
|
||||
if (readingList == null)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(userId, "reading-list-restricted"));
|
||||
}
|
||||
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
var feed = CreateFeed(readingList.Title + " " + await _localizationService.Translate(userId, "reading-list"), $"{apiKey}/reading-list/{readingListId}", apiKey, prefix);
|
||||
SetFeedId(feed, $"reading-list-{readingListId}");
|
||||
|
||||
|
@ -53,6 +53,7 @@ public interface IReadingListRepository
|
||||
Task<int> RemoveReadingListsWithoutSeries();
|
||||
Task<ReadingList?> GetReadingListByTitleAsync(string name, int userId, ReadingListIncludes includes = ReadingListIncludes.Items);
|
||||
Task<IEnumerable<ReadingList>> GetReadingListsByIds(IList<int> ids, ReadingListIncludes includes = ReadingListIncludes.Items);
|
||||
Task<IEnumerable<ReadingList>> GetReadingListsBySeriesId(int seriesId, ReadingListIncludes includes = ReadingListIncludes.Items);
|
||||
}
|
||||
|
||||
public class ReadingListRepository : IReadingListRepository
|
||||
@ -170,7 +171,14 @@ public class ReadingListRepository : IReadingListRepository
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingList>> GetReadingListsBySeriesId(int seriesId, ReadingListIncludes includes = ReadingListIncludes.Items)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(rl => rl.Items.Any(rli => rli.SeriesId == seriesId))
|
||||
.Includes(includes)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
public void Remove(ReadingListItem item)
|
||||
|
@ -50,6 +50,14 @@ public interface IReadingListService
|
||||
|
||||
Task CreateReadingListsFromSeries(int libraryId, int seriesId);
|
||||
Task<string> GenerateReadingListCoverImage(int readingListId);
|
||||
/// <summary>
|
||||
/// Check, and update if needed, all reading lists' AgeRating who contain the passed series
|
||||
/// </summary>
|
||||
/// <param name="seriesId">The series whose age rating is being updated</param>
|
||||
/// <param name="ageRating">The new (uncommited) age rating of the series</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>This method does not commit changes</remarks>
|
||||
Task UpdateReadingListAgeRatingForSeries(int seriesId, AgeRating ageRating);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -96,7 +104,13 @@ public class ReadingListService : IReadingListService
|
||||
{
|
||||
title = $"Volume {Parser.CleanSpecialTitle(item.VolumeNumber)}";
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else if (item.VolumeNumber == Parser.SpecialVolume)
|
||||
{
|
||||
title = specialTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
title = $"Volume {specialTitle}";
|
||||
}
|
||||
}
|
||||
@ -844,4 +858,22 @@ public class ReadingListService : IReadingListService
|
||||
|
||||
return !_directoryService.FileSystem.File.Exists(destFile) ? string.Empty : destFile;
|
||||
}
|
||||
|
||||
public async Task UpdateReadingListAgeRatingForSeries(int seriesId, AgeRating ageRating)
|
||||
{
|
||||
var readingLists = await _unitOfWork.ReadingListRepository.GetReadingListsBySeriesId(seriesId);
|
||||
foreach (var readingList in readingLists)
|
||||
{
|
||||
var seriesIds = readingList.Items.Select(item => item.SeriesId).ToList();
|
||||
seriesIds.Remove(seriesId); // Don't get AgeRating from database
|
||||
|
||||
var maxAgeRating = await _unitOfWork.SeriesRepository.GetMaxAgeRatingFromSeriesAsync(seriesIds);
|
||||
if (ageRating > maxAgeRating)
|
||||
{
|
||||
maxAgeRating = ageRating;
|
||||
}
|
||||
|
||||
readingList.AgeRating = maxAgeRating;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ public class SeriesService : ISeriesService
|
||||
private readonly ILogger<SeriesService> _logger;
|
||||
private readonly IScrobblingService _scrobblingService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IReadingListService _readingListService;
|
||||
|
||||
private readonly NextExpectedChapterDto _emptyExpectedChapter = new NextExpectedChapterDto
|
||||
{
|
||||
@ -59,7 +60,8 @@ public class SeriesService : ISeriesService
|
||||
};
|
||||
|
||||
public SeriesService(IUnitOfWork unitOfWork, IEventHub eventHub, ITaskScheduler taskScheduler,
|
||||
ILogger<SeriesService> logger, IScrobblingService scrobblingService, ILocalizationService localizationService)
|
||||
ILogger<SeriesService> logger, IScrobblingService scrobblingService, ILocalizationService localizationService,
|
||||
IReadingListService readingListService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_eventHub = eventHub;
|
||||
@ -67,6 +69,7 @@ public class SeriesService : ISeriesService
|
||||
_logger = logger;
|
||||
_scrobblingService = scrobblingService;
|
||||
_localizationService = localizationService;
|
||||
_readingListService = readingListService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -191,6 +194,7 @@ public class SeriesService : ISeriesService
|
||||
{
|
||||
series.Metadata.AgeRating = updateSeriesMetadataDto.SeriesMetadata?.AgeRating ?? AgeRating.Unknown;
|
||||
series.Metadata.AgeRatingLocked = true;
|
||||
await _readingListService.UpdateReadingListAgeRatingForSeries(series.Id, series.Metadata.AgeRating);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -42,7 +42,7 @@
|
||||
|
||||
@if (item.releaseDate !== '0001-01-01T00:00:00') {
|
||||
<div class="ps-1 mt-2">
|
||||
Released: {{item.releaseDate | date:'longDate'}}
|
||||
{{t('released-label', {date: item.releaseDate | date:'longDate'})}}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -613,6 +613,7 @@ export class VolumeDetailComponent implements OnInit {
|
||||
});
|
||||
break;
|
||||
case Action.AddToReadingList:
|
||||
this.actionService.addVolumeToReadingList(this.volume!, this.seriesId);
|
||||
break;
|
||||
case Action.Download:
|
||||
if (this.downloadInProgress) return;
|
||||
|
@ -1731,7 +1731,8 @@
|
||||
|
||||
"reading-list-item": {
|
||||
"remove": "{{common.remove}}",
|
||||
"read": "{{common.read}}"
|
||||
"read": "{{common.read}}",
|
||||
"released-label": "Released: {{date}}"
|
||||
},
|
||||
|
||||
"stream-list-item": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user