Random Cleanup + OPDS Base Url Support (#1926)

* Updated a ton of dependencies. PDFs reader got a big update from PDF.js 2.6 -> 3.x

* Rolled back fontawesome update

* Updated to latest angular patch. Fixed search being too long instead of just to the end of the browser screen.

* Fixed alignment on download icon for download indicator in cards

* Include progress information on Want To Read API and when marking something as Read, perform cleanup service on want to read.

* Removed mark-read updating want to read. As there are series restrictions and it could be misleading.

* Tweaked login page spacing when form is dirty

* Replaced an object instantiation

* Commented out a few tests that always break when updating NetVips (but always work)

* Updated ngx-toastr

* Added styles for alerts to Kavita. They were somehow missing. Fixed an issue where when OPDS was disabled, user preferences wouldn't tell them.

* Wired up a reset base url button to match Ip Addresses

* Disable ipAddress and port for docker users

* Removed cache dir since it's kinda pointless currently

* Started the update for OPDS BaseUrl support

* Fixed OPDS url not reflecting base url on localhost

* Added extra plumbing to allow sending a real email when testing a custom service.

* Implemented OPDS support under Base Url. Added pagination to all APIs where applicable.

* Added a swallowing of permission denied on Updating baseurl in index.html for inapplicable users.

* Fixed a bad test
This commit is contained in:
Joe Milazzo 2023-04-14 19:44:35 -05:00 committed by GitHub
parent 1bf4fde58f
commit 21a9f28923
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 2519 additions and 1219 deletions

View file

@ -38,7 +38,6 @@ public class OpdsController : BaseApiController
private readonly XmlSerializer _xmlSerializer;
private readonly XmlSerializer _xmlOpenSearchSerializer;
private const string Prefix = "/api/opds/";
private readonly FilterDto _filterDto = new FilterDto()
{
Formats = new List<MangaFormat>(),
@ -63,7 +62,8 @@ public class OpdsController : BaseApiController
SortOptions = null,
PublicationStatus = new List<PublicationStatus>()
};
private readonly ChapterSortComparer _chapterSortComparer = new ChapterSortComparer();
private readonly ChapterSortComparer _chapterSortComparer = ChapterSortComparer.Default;
private const int PageSize = 20;
public OpdsController(IUnitOfWork unitOfWork, IDownloadService downloadService,
IDirectoryService directoryService, ICacheService cacheService,
@ -80,7 +80,6 @@ public class OpdsController : BaseApiController
_xmlSerializer = new XmlSerializer(typeof(Feed));
_xmlOpenSearchSerializer = new XmlSerializer(typeof(OpenSearchDescription));
}
[HttpPost("{apiKey}")]
@ -90,7 +89,10 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var feed = CreateFeed("Kavita", string.Empty, apiKey);
var (baseUrl, prefix) = await GetPrefix();
var feed = CreateFeed("Kavita", string.Empty, apiKey, prefix, baseUrl);
SetFeedId(feed, "root");
feed.Entries.Add(new FeedEntry()
{
@ -102,7 +104,7 @@ public class OpdsController : BaseApiController
},
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/on-deck"),
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/on-deck"),
}
});
feed.Entries.Add(new FeedEntry()
@ -115,7 +117,7 @@ public class OpdsController : BaseApiController
},
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/recently-added"),
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/recently-added"),
}
});
feed.Entries.Add(new FeedEntry()
@ -128,7 +130,7 @@ public class OpdsController : BaseApiController
},
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/reading-list"),
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/reading-list"),
}
});
feed.Entries.Add(new FeedEntry()
@ -141,7 +143,7 @@ public class OpdsController : BaseApiController
},
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/libraries"),
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/libraries"),
}
});
feed.Entries.Add(new FeedEntry()
@ -154,12 +156,25 @@ public class OpdsController : BaseApiController
},
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/collections"),
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/collections"),
}
});
return CreateXmlResult(SerializeXml(feed));
}
private async Task<Tuple<string, string>> GetPrefix()
{
var baseUrl = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BaseUrl)).Value;
var prefix = "/api/opds/";
if (!Configuration.DefaultBaseUrl.Equals(baseUrl))
{
// We need to update the Prefix to account for baseUrl
prefix = baseUrl + "api/opds/";
}
return new Tuple<string, string>(baseUrl, prefix);
}
[HttpGet("{apiKey}/libraries")]
[Produces("application/xml")]
@ -167,9 +182,10 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var libraries = await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId);
var feed = CreateFeed("All Libraries", $"{apiKey}/libraries", apiKey);
var feed = CreateFeed("All Libraries", $"{prefix}{apiKey}/libraries", apiKey, prefix, baseUrl);
SetFeedId(feed, "libraries");
foreach (var library in libraries)
{
@ -179,7 +195,7 @@ public class OpdsController : BaseApiController
Title = library.Name,
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/libraries/{library.Id}"),
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/libraries/{library.Id}"),
}
});
}
@ -193,16 +209,17 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null) return Unauthorized();
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
IEnumerable<CollectionTagDto> tags = isAdmin ? (await _unitOfWork.CollectionTagRepository.GetAllTagDtosAsync())
var tags = isAdmin ? (await _unitOfWork.CollectionTagRepository.GetAllTagDtosAsync())
: (await _unitOfWork.CollectionTagRepository.GetAllPromotedTagDtosAsync(userId));
var feed = CreateFeed("All Collections", $"{apiKey}/collections", apiKey);
var feed = CreateFeed("All Collections", $"{prefix}{apiKey}/collections", apiKey, prefix, baseUrl);
SetFeedId(feed, "collections");
foreach (var tag in tags)
{
@ -213,9 +230,9 @@ public class OpdsController : BaseApiController
Summary = tag.Summary,
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/collections/{tag.Id}"),
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"/api/image/collection-cover?collectionId={tag.Id}"),
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"/api/image/collection-cover?collectionId={tag.Id}")
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}")
}
});
}
@ -230,6 +247,7 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null) return Unauthorized();
@ -251,20 +269,16 @@ public class OpdsController : BaseApiController
return BadRequest("Collection does not exist or you don't have access");
}
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForCollectionAsync(collectionId, userId, new UserParams()
{
PageNumber = pageNumber,
PageSize = 20
});
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForCollectionAsync(collectionId, userId, GetUserParams(pageNumber));
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(series.Select(s => s.Id));
var feed = CreateFeed(tag.Title + " Collection", $"{apiKey}/collections/{collectionId}", apiKey);
var feed = CreateFeed(tag.Title + " Collection", $"{prefix}{apiKey}/collections/{collectionId}", apiKey, prefix, baseUrl);
SetFeedId(feed, $"collections-{collectionId}");
AddPagination(feed, series, $"{Prefix}{apiKey}/collections/{collectionId}");
AddPagination(feed, series, $"{prefix}{apiKey}/collections/{collectionId}");
foreach (var seriesDto in series)
{
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey));
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl));
}
@ -277,15 +291,13 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var readingLists = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, true, new UserParams()
{
PageNumber = pageNumber
});
var readingLists = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, true, GetUserParams(pageNumber));
var feed = CreateFeed("All Reading Lists", $"{apiKey}/reading-list", apiKey);
var feed = CreateFeed("All Reading Lists", $"{prefix}{apiKey}/reading-list", apiKey, prefix, baseUrl);
SetFeedId(feed, "reading-list");
foreach (var readingListDto in readingLists)
{
@ -296,7 +308,7 @@ public class OpdsController : BaseApiController
Summary = readingListDto.Summary,
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/reading-list/{readingListDto.Id}"),
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/reading-list/{readingListDto.Id}"),
}
});
}
@ -304,12 +316,22 @@ public class OpdsController : BaseApiController
return CreateXmlResult(SerializeXml(feed));
}
private static UserParams GetUserParams(int pageNumber)
{
return new UserParams()
{
PageNumber = pageNumber,
PageSize = PageSize
};
}
[HttpGet("{apiKey}/reading-list/{readingListId}")]
[Produces("application/xml")]
public async Task<IActionResult> GetReadingListItems(int readingListId, string apiKey)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
@ -321,13 +343,15 @@ public class OpdsController : BaseApiController
return BadRequest("Reading list does not exist or you don't have access");
}
var feed = CreateFeed(readingList.Title + " Reading List", $"{apiKey}/reading-list/{readingListId}", apiKey);
var feed = CreateFeed(readingList.Title + " Reading List", $"{prefix}{apiKey}/reading-list/{readingListId}", apiKey, prefix, baseUrl);
SetFeedId(feed, $"reading-list-{readingListId}");
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId)).ToList();
foreach (var item in items)
{
feed.Entries.Add(CreateChapter(apiKey, $"{item.Order} - {item.SeriesName}: {item.Title}", string.Empty, 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, prefix, baseUrl));
}
return CreateXmlResult(SerializeXml(feed));
}
@ -338,6 +362,7 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var library =
(await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId)).SingleOrDefault(l =>
@ -347,20 +372,16 @@ public class OpdsController : BaseApiController
return BadRequest("User does not have access to this library");
}
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, new UserParams()
{
PageNumber = pageNumber,
PageSize = 20
}, _filterDto);
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, GetUserParams(pageNumber), _filterDto);
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(series.Select(s => s.Id));
var feed = CreateFeed(library.Name, $"{apiKey}/libraries/{libraryId}", apiKey);
var feed = CreateFeed(library.Name, $"{apiKey}/libraries/{libraryId}", apiKey, prefix, baseUrl);
SetFeedId(feed, $"library-{library.Name}");
AddPagination(feed, series, $"{Prefix}{apiKey}/libraries/{libraryId}");
AddPagination(feed, series, $"{prefix}{apiKey}/libraries/{libraryId}");
foreach (var seriesDto in series)
{
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey));
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl));
}
return CreateXmlResult(SerializeXml(feed));
@ -372,21 +393,18 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var recentlyAdded = await _unitOfWork.SeriesRepository.GetRecentlyAdded(0, userId, new UserParams()
{
PageNumber = pageNumber,
PageSize = 20
}, _filterDto);
var recentlyAdded = await _unitOfWork.SeriesRepository.GetRecentlyAdded(0, userId, GetUserParams(pageNumber), _filterDto);
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(recentlyAdded.Select(s => s.Id));
var feed = CreateFeed("Recently Added", $"{apiKey}/recently-added", apiKey);
var feed = CreateFeed("Recently Added", $"{prefix}{apiKey}/recently-added", apiKey, prefix, baseUrl);
SetFeedId(feed, "recently-added");
AddPagination(feed, recentlyAdded, $"{Prefix}{apiKey}/recently-added");
AddPagination(feed, recentlyAdded, $"{prefix}{apiKey}/recently-added");
foreach (var seriesDto in recentlyAdded)
{
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey));
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl));
}
return CreateXmlResult(SerializeXml(feed));
@ -398,23 +416,23 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var userParams = new UserParams()
{
PageNumber = pageNumber,
};
var userParams = GetUserParams(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);
var feed = CreateFeed("On Deck", $"{apiKey}/on-deck", apiKey);
var feed = CreateFeed("On Deck", $"{prefix}{apiKey}/on-deck", apiKey, prefix, baseUrl);
SetFeedId(feed, "on-deck");
AddPagination(feed, pagedList, $"{Prefix}{apiKey}/on-deck");
AddPagination(feed, pagedList, $"{prefix}{apiKey}/on-deck");
foreach (var seriesDto in pagedList)
{
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey));
feed.Entries.Add(CreateSeries(seriesDto, seriesMetadatas.First(s => s.SeriesId == seriesDto.Id), apiKey, prefix, baseUrl));
}
return CreateXmlResult(SerializeXml(feed));
@ -426,6 +444,7 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
@ -442,11 +461,11 @@ public class OpdsController : BaseApiController
var series = await _unitOfWork.SeriesRepository.SearchSeries(userId, isAdmin, libraries.Select(l => l.Id).ToArray(), query);
var feed = CreateFeed(query, $"{apiKey}/series?query=" + query, apiKey);
var feed = CreateFeed(query, $"{prefix}{apiKey}/series?query=" + query, apiKey, prefix, baseUrl);
SetFeedId(feed, "search-series");
foreach (var seriesDto in series.Series)
{
feed.Entries.Add(CreateSeries(seriesDto, apiKey));
feed.Entries.Add(CreateSeries(seriesDto, apiKey, prefix, baseUrl));
}
foreach (var collection in series.Collections)
@ -459,11 +478,11 @@ public class OpdsController : BaseApiController
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
Prefix + $"{apiKey}/collections/{collection.Id}"),
$"{prefix}{apiKey}/collections/{collection.Id}"),
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image,
$"/api/image/collection-cover?collectionId={collection.Id}"),
$"{baseUrl}api/image/collection-cover?collectionId={collection.Id}"),
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image,
$"/api/image/collection-cover?collectionId={collection.Id}")
$"{baseUrl}api/image/collection-cover?collectionId={collection.Id}")
}
});
}
@ -477,7 +496,7 @@ public class OpdsController : BaseApiController
Summary = readingListDto.Summary,
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/reading-list/{readingListDto.Id}"),
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/reading-list/{readingListDto.Id}"),
}
});
}
@ -497,6 +516,7 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var feed = new OpenSearchDescription()
{
ShortName = "Search",
@ -504,7 +524,7 @@ public class OpdsController : BaseApiController
Url = new SearchLink()
{
Type = FeedLinkType.AtomAcquisition,
Template = $"{Prefix}{apiKey}/series?query=" + "{searchTerms}"
Template = $"{prefix}{apiKey}/series?query=" + "{searchTerms}"
}
};
@ -520,12 +540,13 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
var feed = CreateFeed(series.Name + " - Storyline", $"{apiKey}/series/{series.Id}", apiKey);
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, $"/api/image/series-cover?seriesId={seriesId}"));
feed.Links.Add(CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={seriesId}"));
var seriesDetail = await _seriesService.GetSeriesDetail(seriesId, userId);
foreach (var volume in seriesDetail.Volumes)
@ -539,7 +560,7 @@ public class OpdsController : BaseApiController
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapter.Id);
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, volume.Id, chapter.Id, mangaFile, series, chapterTest, apiKey));
feed.Entries.Add(await CreateChapterWithFile(seriesId, volume.Id, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
}
}
@ -551,7 +572,7 @@ public class OpdsController : BaseApiController
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(storylineChapter.Id);
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, storylineChapter.VolumeId, storylineChapter.Id, mangaFile, series, chapterTest, apiKey));
feed.Entries.Add(await CreateChapterWithFile(seriesId, storylineChapter.VolumeId, storylineChapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
}
}
@ -561,7 +582,7 @@ public class OpdsController : BaseApiController
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(special.Id);
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, special.VolumeId, special.Id, mangaFile, series, chapterTest, apiKey));
feed.Entries.Add(await CreateChapterWithFile(seriesId, special.VolumeId, special.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
}
}
@ -574,6 +595,7 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
@ -582,7 +604,7 @@ public class OpdsController : BaseApiController
(await _unitOfWork.ChapterRepository.GetChaptersAsync(volumeId)).OrderBy(x => double.Parse(x.Number),
_chapterSortComparer);
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s ",
$"{apiKey}/series/{seriesId}/volume/{volumeId}", apiKey);
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}", apiKey, prefix, baseUrl);
SetFeedId(feed, $"series-{series.Id}-volume-{volume.Id}-{SeriesService.FormatChapterName(libraryType)}s");
foreach (var chapter in chapters)
{
@ -590,7 +612,7 @@ public class OpdsController : BaseApiController
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapter.Id);
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, volumeId, chapter.Id, mangaFile, series, chapterTest, apiKey));
feed.Entries.Add(await CreateChapterWithFile(seriesId, volumeId, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
}
}
@ -603,6 +625,7 @@ public class OpdsController : BaseApiController
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
@ -612,11 +635,11 @@ public class OpdsController : BaseApiController
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId);
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s",
$"{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}", apiKey);
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}", apiKey, prefix, baseUrl);
SetFeedId(feed, $"series-{series.Id}-volume-{volumeId}-{SeriesService.FormatChapterName(libraryType)}-{chapterId}-files");
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, volumeId, chapterId, mangaFile, series, chapter, apiKey));
feed.Entries.Add(await CreateChapterWithFile(seriesId, volumeId, chapterId, mangaFile, series, chapter, apiKey, prefix, baseUrl));
}
return CreateXmlResult(SerializeXml(feed));
@ -694,7 +717,7 @@ public class OpdsController : BaseApiController
feed.StartIndex = (Math.Max(list.CurrentPage - 1, 0) * list.PageSize) + 1;
}
private static FeedEntry CreateSeries(SeriesDto seriesDto, SeriesMetadataDto metadata, string apiKey)
private static FeedEntry CreateSeries(SeriesDto seriesDto, SeriesMetadataDto metadata, string apiKey, string prefix, string baseUrl)
{
return new FeedEntry()
{
@ -704,7 +727,7 @@ public class OpdsController : BaseApiController
Authors = metadata.Writers.Select(p => new FeedAuthor()
{
Name = p.Name,
Uri = "http://opds-spec.org/author"
Uri = "http://opds-spec.org/author/" + p.Id
}).ToList(),
Categories = metadata.Genres.Select(g => new FeedCategory()
{
@ -713,14 +736,14 @@ public class OpdsController : BaseApiController
}).ToList(),
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/series/{seriesDto.Id}"),
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"/api/image/series-cover?seriesId={seriesDto.Id}"),
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"/api/image/series-cover?seriesId={seriesDto.Id}")
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}")
}
};
}
private static FeedEntry CreateSeries(SearchResultDto searchResultDto, string apiKey)
private static FeedEntry CreateSeries(SearchResultDto searchResultDto, string apiKey, string prefix, string baseUrl)
{
return new FeedEntry()
{
@ -728,14 +751,14 @@ public class OpdsController : BaseApiController
Title = $"{searchResultDto.Name} ({searchResultDto.Format})",
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, Prefix + $"{apiKey}/series/{searchResultDto.SeriesId}"),
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"/api/image/series-cover?seriesId={searchResultDto.SeriesId}"),
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"/api/image/series-cover?seriesId={searchResultDto.SeriesId}")
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}")
}
};
}
private static FeedEntry CreateChapter(string apiKey, string title, string summary, int chapterId, int volumeId, int seriesId)
private static FeedEntry CreateChapter(string apiKey, string title, string summary, int chapterId, int volumeId, int seriesId, string prefix, string baseUrl)
{
return new FeedEntry()
{
@ -745,16 +768,16 @@ public class OpdsController : BaseApiController
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation,
Prefix + $"{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}"),
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}"),
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image,
$"/api/image/chapter-cover?chapterId={chapterId}"),
$"{baseUrl}api/image/chapter-cover?chapterId={chapterId}"),
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image,
$"/api/image/chapter-cover?chapterId={chapterId}")
$"{baseUrl}api/image/chapter-cover?chapterId={chapterId}")
}
};
}
private async Task<FeedEntry> CreateChapterWithFile(int seriesId, int volumeId, int chapterId, MangaFile mangaFile, SeriesDto series, ChapterDto chapter, string apiKey)
private async Task<FeedEntry> CreateChapterWithFile(int seriesId, int volumeId, int chapterId, MangaFile mangaFile, SeriesDto series, ChapterDto chapter, string apiKey, string prefix, string baseUrl)
{
var fileSize =
mangaFile.Bytes > 0 ? DirectoryService.GetHumanReadableBytes(mangaFile.Bytes) :
@ -787,7 +810,7 @@ public class OpdsController : BaseApiController
// Chunky requires a file at the end. Our API ignores this
var accLink =
CreateLink(FeedLinkRelation.Acquisition, fileType,
$"{Prefix}{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}/download/{filename}",
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}/download/{filename}",
filename);
accLink.TotalPages = chapter.Pages;
@ -800,11 +823,11 @@ public class OpdsController : BaseApiController
Format = mangaFile.Format.ToString(),
Links = new List<FeedLink>()
{
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"/api/image/chapter-cover?chapterId={chapterId}"),
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"/api/image/chapter-cover?chapterId={chapterId}"),
CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/chapter-cover?chapterId={chapterId}"),
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{baseUrl}api/image/chapter-cover?chapterId={chapterId}"),
// 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)
await CreatePageStreamLink(series.LibraryId, seriesId, volumeId, chapterId, mangaFile, apiKey, prefix)
},
Content = new FeedEntryContent()
{
@ -894,13 +917,14 @@ public class OpdsController : BaseApiController
throw new KavitaException("User does not exist");
}
private async Task<FeedLink> CreatePageStreamLink(int libraryId, int seriesId, int volumeId, int chapterId, MangaFile mangaFile, string apiKey)
private async Task<FeedLink> CreatePageStreamLink(int libraryId, int seriesId, int volumeId, int chapterId, MangaFile mangaFile, string apiKey, string prefix)
{
var userId = await GetUser(apiKey);
var progress = await _unitOfWork.AppUserProgressRepository.GetUserProgressDtoAsync(chapterId, userId);
// TODO: Type could be wrong
var link = CreateLink(FeedLinkRelation.Stream, "image/jpeg",
$"{Prefix}{apiKey}/image?libraryId={libraryId}&seriesId={seriesId}&volumeId={volumeId}&chapterId={chapterId}&pageNumber=" + "{pageNumber}");
$"{prefix}{apiKey}/image?libraryId={libraryId}&seriesId={seriesId}&volumeId={volumeId}&chapterId={chapterId}&pageNumber=" + "{pageNumber}");
link.TotalPages = mangaFile.Pages;
if (progress != null)
{
@ -922,21 +946,21 @@ public class OpdsController : BaseApiController
};
}
private static Feed CreateFeed(string title, string href, string apiKey)
private static Feed CreateFeed(string title, string href, string apiKey, string prefix, string baseUrl)
{
var link = CreateLink(FeedLinkRelation.Self, string.IsNullOrEmpty(href) ?
FeedLinkType.AtomNavigation :
FeedLinkType.AtomAcquisition, Prefix + href);
FeedLinkType.AtomAcquisition, prefix + href);
return new Feed()
{
Title = title,
Icon = Prefix + $"{apiKey}/favicon",
Icon = $"{prefix}{apiKey}/favicon",
Links = new List<FeedLink>()
{
link,
CreateLink(FeedLinkRelation.Start, FeedLinkType.AtomNavigation, Prefix + apiKey),
CreateLink(FeedLinkRelation.Search, FeedLinkType.AtomSearch, Prefix + $"{apiKey}/search")
CreateLink(FeedLinkRelation.Start, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}"),
CreateLink(FeedLinkRelation.Search, FeedLinkType.AtomSearch, $"{prefix}{apiKey}/search")
},
};
}

View file

@ -76,7 +76,7 @@ public class SettingsController : BaseApiController
/// <returns></returns>
[Authorize(Policy = "RequireAdminRole")]
[HttpPost("reset-ip-addresses")]
public async Task<ActionResult<ServerSettingDto>> ResetIPAddressesSettings()
public async Task<ActionResult<ServerSettingDto>> ResetIpAddressesSettings()
{
_logger.LogInformation("{UserName} is resetting IP Addresses Setting", User.GetUsername());
var ipAddresses = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.IpAddresses);
@ -91,6 +91,28 @@ public class SettingsController : BaseApiController
return Ok(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync());
}
/// <summary>
/// Resets the Base url
/// </summary>
/// <returns></returns>
[Authorize(Policy = "RequireAdminRole")]
[HttpPost("reset-base-url")]
public async Task<ActionResult<ServerSettingDto>> ResetBaseUrlSettings()
{
_logger.LogInformation("{UserName} is resetting Base Url Setting", User.GetUsername());
var baseUrl = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BaseUrl);
baseUrl.Value = Configuration.DefaultBaseUrl;
_unitOfWork.SettingsRepository.Update(baseUrl);
if (!await _unitOfWork.CommitAsync())
{
await _unitOfWork.RollbackAsync();
}
Configuration.BaseUrl = baseUrl.Value;
return Ok(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync());
}
/// <summary>
/// Resets the email service url
/// </summary>
@ -116,7 +138,9 @@ public class SettingsController : BaseApiController
[HttpPost("test-email-url")]
public async Task<ActionResult<EmailTestResultDto>> TestEmailServiceUrl(TestEmailDto dto)
{
return Ok(await _emailService.TestConnectivity(dto.Url));
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId());
var emailService = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.EmailServiceUrl)).Value;
return Ok(await _emailService.TestConnectivity(dto.Url, user!.Email, !emailService.Equals(EmailService.DefaultApiUrl)));
}
@ -177,7 +201,7 @@ public class SettingsController : BaseApiController
}
setting.Value = updateSettingsDto.IpAddresses;
// IpAddesses is managed in appSetting.json
// IpAddresses is managed in appSetting.json
Configuration.IpAddresses = updateSettingsDto.IpAddresses;
_unitOfWork.SettingsRepository.Update(setting);
}

View file

@ -36,6 +36,9 @@ public class WantToReadController : BaseApiController
userParams ??= new UserParams();
var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(User.GetUserId(), userParams, filterDto);
Response.AddPaginationHeader(pagedList.CurrentPage, pagedList.PageSize, pagedList.TotalCount, pagedList.TotalPages);
await _unitOfWork.SeriesRepository.AddSeriesModifiers(User.GetUserId(), pagedList);
return Ok(pagedList);
}