OPDS Enhancements (#1687)

* Bump express from 4.17.2 to 4.18.2 in /UI/Web

Bumps [express](https://github.com/expressjs/express) from 4.17.2 to 4.18.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.17.2...4.18.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump decode-uri-component from 0.2.0 to 0.2.2 in /UI/Web

Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump qs and express in /UI/Web

Bumps [qs](https://github.com/ljharb/qs) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `qs` from 6.5.3 to 6.11.0
- [Release notes](https://github.com/ljharb/qs/releases)
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.5.3...v6.11.0)

Updates `express` from 4.17.2 to 4.18.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.17.2...4.18.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Added genre and authors to Series level, added summary to volume and chapter level.

Force order on reading list title as Chunky enforces their own sort order and doesn't respect the spec.

* Moved all the reading list formatting logic to the backend. This allows us to re-use the UI logic for OPDS streams.

* Fixed a broken unit test

* Code smells

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Joe Milazzo 2022-12-10 11:07:55 -06:00 committed by GitHub
parent fbd9b36e35
commit 1a729adf40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 369 additions and 344 deletions

View file

@ -253,6 +253,7 @@ public class OpdsController : BaseApiController
PageNumber = pageNumber,
PageSize = 20
});
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(series.Select(s => s.Id));
var feed = CreateFeed(tag.Title + " Collection", $"{apiKey}/collections/{collectionId}", apiKey);
SetFeedId(feed, $"collections-{collectionId}");
@ -260,7 +261,7 @@ public class OpdsController : BaseApiController
foreach (var seriesDto in series)
{
feed.Entries.Add(CreateSeries(seriesDto, apiKey));
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey));
}
@ -322,7 +323,7 @@ public class OpdsController : BaseApiController
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId)).ToList();
foreach (var item in items)
{
feed.Entries.Add(CreateChapter(apiKey, $"{item.SeriesName} Chapter {item.ChapterNumber}", item.ChapterId, item.VolumeId, item.SeriesId));
feed.Entries.Add(CreateChapter(apiKey, $"{item.Order} - {item.SeriesName}: {item.Title}", string.Empty, item.ChapterId, item.VolumeId, item.SeriesId));
}
return CreateXmlResult(SerializeXml(feed));
}
@ -347,6 +348,7 @@ public class OpdsController : BaseApiController
PageNumber = pageNumber,
PageSize = 20
}, _filterDto);
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(series.Select(s => s.Id));
var feed = CreateFeed(library.Name, $"{apiKey}/libraries/{libraryId}", apiKey);
SetFeedId(feed, $"library-{library.Name}");
@ -354,7 +356,7 @@ public class OpdsController : BaseApiController
foreach (var seriesDto in series)
{
feed.Entries.Add(CreateSeries(seriesDto, apiKey));
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey));
}
return CreateXmlResult(SerializeXml(feed));
@ -372,6 +374,7 @@ public class OpdsController : BaseApiController
PageNumber = pageNumber,
PageSize = 20
}, _filterDto);
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(recentlyAdded.Select(s => s.Id));
var feed = CreateFeed("Recently Added", $"{apiKey}/recently-added", apiKey);
SetFeedId(feed, "recently-added");
@ -379,7 +382,7 @@ public class OpdsController : BaseApiController
foreach (var seriesDto in recentlyAdded)
{
feed.Entries.Add(CreateSeries(seriesDto, apiKey));
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey));
}
return CreateXmlResult(SerializeXml(feed));
@ -397,6 +400,7 @@ public class OpdsController : BaseApiController
PageNumber = pageNumber,
};
var pagedList = await _unitOfWork.SeriesRepository.GetOnDeck(userId, 0, userParams, _filterDto);
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(pagedList.Select(s => s.Id));
Response.AddPaginationHeader(pagedList.CurrentPage, pagedList.PageSize, pagedList.TotalCount, pagedList.TotalPages);
@ -406,7 +410,7 @@ public class OpdsController : BaseApiController
foreach (var seriesDto in pagedList)
{
feed.Entries.Add(CreateSeries(seriesDto, apiKey));
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey));
}
return CreateXmlResult(SerializeXml(feed));
@ -527,7 +531,7 @@ public class OpdsController : BaseApiController
if (volume.Chapters.Count == 1)
{
var firstChapter = volume.Chapters.First();
var chapter = CreateChapter(apiKey, volume.Name, firstChapter.Id, volume.Id, seriesId);
var chapter = CreateChapter(apiKey, volume.Name, firstChapter.Summary, firstChapter.Id, volume.Id, seriesId);
chapter.Id = firstChapter.Id.ToString();
feed.Entries.Add(chapter);
}
@ -540,12 +544,12 @@ public class OpdsController : BaseApiController
foreach (var storylineChapter in seriesDetail.StorylineChapters.Where(c => !c.IsSpecial))
{
feed.Entries.Add(CreateChapter(apiKey, storylineChapter.Title, storylineChapter.Id, storylineChapter.VolumeId, seriesId));
feed.Entries.Add(CreateChapter(apiKey, storylineChapter.Title, storylineChapter.Summary, storylineChapter.Id, storylineChapter.VolumeId, seriesId));
}
foreach (var special in seriesDetail.Specials)
{
feed.Entries.Add(CreateChapter(apiKey, special.Title, special.Id, special.VolumeId, seriesId));
feed.Entries.Add(CreateChapter(apiKey, special.Title, special.Summary, special.Id, special.VolumeId, seriesId));
}
return CreateXmlResult(SerializeXml(feed));
@ -679,13 +683,23 @@ public class OpdsController : BaseApiController
feed.StartIndex = (Math.Max(list.CurrentPage - 1, 0) * list.PageSize) + 1;
}
private static FeedEntry CreateSeries(SeriesDto seriesDto, string apiKey)
private static FeedEntry CreateSeries(SeriesDto seriesDto, SeriesMetadataDto metadata, string apiKey)
{
return new FeedEntry()
{
Id = seriesDto.Id.ToString(),
Title = $"{seriesDto.Name} ({seriesDto.Format})",
Summary = seriesDto.Summary,
Authors = metadata.Writers.Select(p => new FeedAuthor()
{
Name = p.Name,
Uri = "http://opds-spec.org/author"
}).ToList(),
Categories = metadata.Genres.Select(g => new FeedCategory()
{
Label = g.Title,
Term = string.Empty
}).ToList(),
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/series/{seriesDto.Id}"),
@ -716,6 +730,7 @@ public class OpdsController : BaseApiController
{
Id = volumeDto.Id.ToString(),
Title = volumeDto.Name,
Summary = volumeDto.Chapters.First().Summary ?? string.Empty,
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
@ -728,12 +743,13 @@ public class OpdsController : BaseApiController
};
}
private static FeedEntry CreateChapter(string apiKey, string title, int chapterId, int volumeId, int seriesId)
private static FeedEntry CreateChapter(string apiKey, string title, string summary, int chapterId, int volumeId, int seriesId)
{
return new FeedEntry()
{
Id = chapterId.ToString(),
Title = title,
Summary = summary ?? string.Empty,
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,