From 5c1e9c052127ce153b38a8e3b67e93d3e7f80976 Mon Sep 17 00:00:00 2001 From: Joe Milazzo Date: Thu, 13 Apr 2023 15:07:11 -0500 Subject: [PATCH] Manga Reader Fixes (#1925) * Fixed up an issue where image might be cut off in fit to height * Removed some calls to backend for translating age rating to a string * Fixed an issue with sizing on page splitting right to left. * Ensure all image access requires apikey * Removed a TODO --- API/Controllers/ImageController.cs | 36 +++++---- API/Controllers/ReaderController.cs | 20 +++-- UI/Web/src/_manga-reader-common.scss | 7 +- UI/Web/src/app/_services/image.service.ts | 18 +++-- UI/Web/src/app/_services/reader.service.ts | 26 +++++-- .../series-info-cards.component.html | 2 +- .../canvas-renderer.component.html | 2 +- .../canvas-renderer.component.ts | 9 ++- .../manga-reader/manga-reader.component.ts | 1 - UI/Web/src/app/pipe/age-rating.pipe.ts | 23 +++++- openapi.json | 73 ++++++++++++++++++- 11 files changed, 169 insertions(+), 48 deletions(-) diff --git a/API/Controllers/ImageController.cs b/API/Controllers/ImageController.cs index 4dd538c14..a158c3bfb 100644 --- a/API/Controllers/ImageController.cs +++ b/API/Controllers/ImageController.cs @@ -34,9 +34,10 @@ public class ImageController : BaseApiController /// /// [HttpGet("chapter-cover")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"chapterId"})] - public async Task GetChapterCoverImage(int chapterId) + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"chapterId", "apiKey"})] + public async Task GetChapterCoverImage(int chapterId, string apiKey) { + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); var format = _directoryService.FileSystem.Path.GetExtension(path); @@ -50,9 +51,10 @@ public class ImageController : BaseApiController /// /// [HttpGet("library-cover")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"libraryId"})] - public async Task GetLibraryCoverImage(int libraryId) + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"libraryId", "apiKey"})] + public async Task GetLibraryCoverImage(int libraryId, string apiKey) { + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); var format = _directoryService.FileSystem.Path.GetExtension(path); @@ -66,9 +68,10 @@ public class ImageController : BaseApiController /// /// [HttpGet("volume-cover")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"volumeId"})] - public async Task GetVolumeCoverImage(int volumeId) + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"volumeId", "apiKey"})] + public async Task GetVolumeCoverImage(int volumeId, string apiKey) { + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); var format = _directoryService.FileSystem.Path.GetExtension(path); @@ -81,10 +84,11 @@ public class ImageController : BaseApiController /// /// Id of Series /// - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"seriesId"})] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"seriesId", "apiKey"})] [HttpGet("series-cover")] - public async Task GetSeriesCoverImage(int seriesId) + public async Task GetSeriesCoverImage(int seriesId, string apiKey) { + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); var format = _directoryService.FileSystem.Path.GetExtension(path); @@ -100,9 +104,10 @@ public class ImageController : BaseApiController /// /// [HttpGet("collection-cover")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"collectionTagId"})] - public async Task GetCollectionCoverImage(int collectionTagId) + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"collectionTagId", "apiKey"})] + public async Task GetCollectionCoverImage(int collectionTagId, string apiKey) { + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); var format = _directoryService.FileSystem.Path.GetExtension(path); @@ -116,9 +121,10 @@ public class ImageController : BaseApiController /// /// [HttpGet("readinglist-cover")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"readingListId"})] - public async Task GetReadingListCoverImage(int readingListId) + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"readingListId", "apiKey"})] + public async Task GetReadingListCoverImage(int readingListId, string apiKey) { + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId)); if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image"); var format = _directoryService.FileSystem.Path.GetExtension(path); @@ -139,6 +145,7 @@ public class ImageController : BaseApiController public async Task GetBookmarkImage(int chapterId, int pageNum, string apiKey) { var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey); + if (userId == 0) return BadRequest(); var bookmark = await _unitOfWork.UserRepository.GetBookmarkForPage(pageNum, chapterId, userId); if (bookmark == null) return BadRequest("Bookmark does not exist"); @@ -157,9 +164,10 @@ public class ImageController : BaseApiController /// [Authorize(Policy="RequireAdminRole")] [HttpGet("cover-upload")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"filename"})] - public ActionResult GetCoverUploadImage(string filename) + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"filename", "apiKey"})] + public async Task GetCoverUploadImage(string filename, string apiKey) { + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); if (filename.Contains("..")) return BadRequest("Invalid Filename"); var path = Path.Join(_directoryService.TempDirectory, filename); diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index ded9345b0..4311448b3 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -57,9 +57,10 @@ public class ReaderController : BaseApiController /// /// [HttpGet("pdf")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)] - public async Task GetPdf(int chapterId) + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId", "apiKey"})] + public async Task GetPdf(int chapterId, string apiKey) { + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); var chapter = await _cacheService.Ensure(chapterId); if (chapter == null) return BadRequest("There was an issue finding pdf file for reading"); @@ -89,14 +90,16 @@ public class ReaderController : BaseApiController /// This will cache the chapter images for reading /// Chapter Id /// Page in question + /// User's API Key for authentication /// Should Kavita extract pdf into images. Defaults to false. /// [HttpGet("image")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId","page", "extractPdf", "apiKey"})] [AllowAnonymous] - public async Task GetImage(int chapterId, int page, bool extractPdf = false) + public async Task GetImage(int chapterId, int page, string apiKey, bool extractPdf = false) { if (page < 0) page = 0; + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); var chapter = await _cacheService.Ensure(chapterId, extractPdf); if (chapter == null) return BadRequest("There was an issue finding image file for reading"); @@ -116,10 +119,11 @@ public class ReaderController : BaseApiController } [HttpGet("thumbnail")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId", "pageNum", "apiKey"})] [AllowAnonymous] - public async Task GetThumbnail(int chapterId, int pageNum) + public async Task GetThumbnail(int chapterId, int pageNum, string apiKey) { + if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest(); var chapter = await _cacheService.Ensure(chapterId, true); if (chapter == null) return BadRequest("There was an issue extracting images from chapter"); var images = _cacheService.GetCachedPages(chapterId); @@ -138,12 +142,13 @@ public class ReaderController : BaseApiController /// We must use api key as bookmarks could be leaked to other users via the API /// [HttpGet("bookmark-image")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"seriesId", "page", "apiKey"})] [AllowAnonymous] public async Task GetBookmarkImage(int seriesId, string apiKey, int page) { if (page < 0) page = 0; var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey); + if (userId == 0) return BadRequest(); var totalPages = await _cacheService.CacheBookmarkForSeries(userId, seriesId); if (page > totalPages) @@ -750,6 +755,7 @@ public class ReaderController : BaseApiController /// /// [HttpGet("time-left")] + [ResponseCache(CacheProfileName = "Hour", VaryByQueryKeys = new string[] { "seriesId"})] public async Task> GetEstimateToCompletion(int seriesId) { var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()); diff --git a/UI/Web/src/_manga-reader-common.scss b/UI/Web/src/_manga-reader-common.scss index 631310e54..5e385274d 100644 --- a/UI/Web/src/_manga-reader-common.scss +++ b/UI/Web/src/_manga-reader-common.scss @@ -1,3 +1,5 @@ +$scrollbarHeight: 34px; + img { user-select: none; } @@ -12,8 +14,9 @@ img { } &.full-height { - height: calc(100vh - 34px); // 34px is the height of the horizontal scrollbar that will appear - display: flex; // changed from inline-block to fix the centering on tablets not working + height: calc(100vh - $scrollbarHeight); + display: flex; + align-content: center; } &.original { diff --git a/UI/Web/src/app/_services/image.service.ts b/UI/Web/src/app/_services/image.service.ts index 3cc3a0355..0a54b00ac 100644 --- a/UI/Web/src/app/_services/image.service.ts +++ b/UI/Web/src/app/_services/image.service.ts @@ -13,6 +13,7 @@ export class ImageService implements OnDestroy { baseUrl = environment.apiUrl; apiKey: string = ''; + encodedKey: string = ''; public placeholderImage = 'assets/images/image-placeholder-min.png'; public errorImage = 'assets/images/error-placeholder2-min.png'; public resetCoverImage = 'assets/images/image-reset-cover-min.png'; @@ -33,6 +34,7 @@ export class ImageService implements OnDestroy { this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => { if (user) { this.apiKey = user.apiKey; + this.encodedKey = encodeURIComponent(this.apiKey); } }); } @@ -62,35 +64,35 @@ export class ImageService implements OnDestroy { } getLibraryCoverImage(libraryId: number) { - return this.baseUrl + 'image/library-cover?libraryId=' + libraryId; + return `${this.baseUrl}image/library-cover?libraryId=${libraryId}&apiKey=${this.encodedKey}`; } getVolumeCoverImage(volumeId: number) { - return this.baseUrl + 'image/volume-cover?volumeId=' + volumeId; + return `${this.baseUrl}image/volume-cover?volumeId=${volumeId}&apiKey=${this.encodedKey}`; } getSeriesCoverImage(seriesId: number) { - return this.baseUrl + 'image/series-cover?seriesId=' + seriesId; + return `${this.baseUrl}image/series-cover?seriesId=${seriesId}&apiKey=${this.encodedKey}`; } getCollectionCoverImage(collectionTagId: number) { - return this.baseUrl + 'image/collection-cover?collectionTagId=' + collectionTagId; + return `${this.baseUrl}image/collection-cover?collectionTagId=${collectionTagId}&apiKey=${this.encodedKey}`; } getReadingListCoverImage(readingListId: number) { - return this.baseUrl + 'image/readinglist-cover?readingListId=' + readingListId; + return `${this.baseUrl}image/readinglist-cover?readingListId=${readingListId}&apiKey=${this.encodedKey}`; } getChapterCoverImage(chapterId: number) { - return this.baseUrl + 'image/chapter-cover?chapterId=' + chapterId; + return `${this.baseUrl}image/chapter-cover?chapterId=${chapterId}&apiKey=${this.encodedKey}`; } getBookmarkedImage(chapterId: number, pageNum: number) { - return this.baseUrl + 'image/bookmark?chapterId=' + chapterId + '&pageNum=' + pageNum + '&apiKey=' + encodeURIComponent(this.apiKey); + return `${this.baseUrl}image/bookmark?chapterId=${chapterId}&apiKey=${this.encodedKey}&pageNum=${pageNum}`; } getCoverUploadImage(filename: string) { - return this.baseUrl + 'image/cover-upload?filename=' + encodeURIComponent(filename); + return `${this.baseUrl}image/cover-upload?filename=${encodeURIComponent(filename)}&apiKey=${this.encodedKey}`; } updateErroredImage(event: any) { diff --git a/UI/Web/src/app/_services/reader.service.ts b/UI/Web/src/app/_services/reader.service.ts index e435afd35..7da1e85bc 100644 --- a/UI/Web/src/app/_services/reader.service.ts +++ b/UI/Web/src/app/_services/reader.service.ts @@ -16,6 +16,9 @@ import { FilterUtilitiesService } from '../shared/_services/filter-utilities.ser import { FileDimension } from '../manga-reader/_models/file-dimension'; import screenfull from 'screenfull'; import { TextResonse } from '../_types/text-response'; +import { AccountService } from './account.service'; +import { Subject, takeUntil } from 'rxjs'; +import { OnDestroy } from '@angular/core'; export const CHAPTER_ID_DOESNT_EXIST = -1; export const CHAPTER_ID_NOT_FETCHED = -2; @@ -23,16 +26,29 @@ export const CHAPTER_ID_NOT_FETCHED = -2; @Injectable({ providedIn: 'root' }) -export class ReaderService { +export class ReaderService implements OnDestroy { baseUrl = environment.apiUrl; + encodedKey: string = ''; + private onDestroy: Subject = new Subject(); // Override background color for reader and restore it onDestroy private originalBodyColor!: string; constructor(private httpClient: HttpClient, private router: Router, private location: Location, private utilityService: UtilityService, - private filterUtilitySerivce: FilterUtilitiesService) { } + private filterUtilitySerivce: FilterUtilitiesService, private accountService: AccountService) { + this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => { + if (user) { + this.encodedKey = encodeURIComponent(user.apiKey); + } + }); + } + + ngOnDestroy() { + this.onDestroy.next(); + this.onDestroy.complete(); + } getNavigationArray(libraryId: number, seriesId: number, chapterId: number, format: MangaFormat) { if (format === undefined) format = MangaFormat.ARCHIVE; @@ -47,7 +63,7 @@ export class ReaderService { } downloadPdf(chapterId: number) { - return this.baseUrl + 'reader/pdf?chapterId=' + chapterId; + return `${this.baseUrl}reader/pdf?chapterId=${chapterId}&apiKey=${this.encodedKey}`; } bookmark(seriesId: number, volumeId: number, chapterId: number, page: number) { @@ -98,11 +114,11 @@ export class ReaderService { } getPageUrl(chapterId: number, page: number) { - return this.baseUrl + 'reader/image?chapterId=' + chapterId + '&page=' + page; + return `${this.baseUrl}reader/image?chapterId=${chapterId}&apiKey=${this.encodedKey}&page=${page}`; } getThumbnailUrl(chapterId: number, page: number) { - return this.baseUrl + 'reader/thumbnail?chapterId=' + chapterId + '&page=' + page; + return `${this.baseUrl}reader/thumbnail?chapterId=${chapterId}&apiKey=${this.encodedKey}&page=${page}`; } getBookmarkPageUrl(seriesId: number, apiKey: string, page: number) { diff --git a/UI/Web/src/app/cards/series-info-cards/series-info-cards.component.html b/UI/Web/src/app/cards/series-info-cards/series-info-cards.component.html index 09c5cac07..524745e1d 100644 --- a/UI/Web/src/app/cards/series-info-cards/series-info-cards.component.html +++ b/UI/Web/src/app/cards/series-info-cards/series-info-cards.component.html @@ -12,7 +12,7 @@
- {{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}} + {{this.seriesMetadata.ageRating | ageRating | async}}
diff --git a/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.html b/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.html index c72f27f42..bf2da9d5e 100644 --- a/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.html +++ b/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.html @@ -1,6 +1,6 @@
- +
diff --git a/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.ts b/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.ts index b759d5f68..f6aca4bdc 100644 --- a/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.ts +++ b/UI/Web/src/app/manga-reader/_components/canvas-renderer/canvas-renderer.component.ts @@ -51,7 +51,7 @@ export class CanvasRendererComponent implements OnInit, AfterViewInit, OnDestroy constructor(private readonly cdRef: ChangeDetectorRef, private mangaReaderService: ManagaReaderService, private readerService: ReaderService) { } ngOnInit(): void { - this.readerSettings$.pipe(takeUntil(this.onDestroy), tap(value => { + this.readerSettings$.pipe(takeUntil(this.onDestroy), tap((value: ReaderSetting) => { this.fit = value.fitting; this.pageSplit = value.pageSplit; this.layoutMode = value.layoutMode; @@ -70,9 +70,9 @@ export class CanvasRendererComponent implements OnInit, AfterViewInit, OnDestroy this.imageFitClass$ = this.readerSettings$.pipe( takeUntil(this.onDestroy), - map(values => values.fitting), + map((values: ReaderSetting) => values.fitting), map(fit => { - if (fit === FITTING_OPTION.WIDTH || this.layoutMode === LayoutMode.Single) return fit; + if (fit === FITTING_OPTION.WIDTH) return fit; // || this.layoutMode === LayoutMode.Single (so that we can check the wide stuff) if (this.canvasImage === null) return fit; // Would this ever execute given that we perform splitting only in this renderer? @@ -181,9 +181,10 @@ export class CanvasRendererComponent implements OnInit, AfterViewInit, OnDestroy const needsSplitting = this.updateSplitPage(); if (!needsSplitting) return; - if (this.currentImageSplitPart === SPLIT_PAGE_PART.NO_SPLIT) return; this.renderWithCanvas = true; + if (this.currentImageSplitPart === SPLIT_PAGE_PART.NO_SPLIT) return; + this.setCanvasSize(); if (needsSplitting && this.currentImageSplitPart === SPLIT_PAGE_PART.LEFT_PART) { diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts index 0545953bb..b6ed5d048 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts @@ -392,7 +392,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { } get SplitIconClass() { - // TODO: make this a pipe if (this.mangaReaderService.isSplitLeftToRight(this.pageSplitOption)) { return 'left-side'; } else if (this.mangaReaderService.isNoSplit(this.pageSplitOption)) { diff --git a/UI/Web/src/app/pipe/age-rating.pipe.ts b/UI/Web/src/app/pipe/age-rating.pipe.ts index f810692d9..eee1b9e32 100644 --- a/UI/Web/src/app/pipe/age-rating.pipe.ts +++ b/UI/Web/src/app/pipe/age-rating.pipe.ts @@ -2,23 +2,38 @@ import { Pipe, PipeTransform } from '@angular/core'; import { Observable, of } from 'rxjs'; import { AgeRating } from '../_models/metadata/age-rating'; import { AgeRatingDto } from '../_models/metadata/age-rating-dto'; -import { MetadataService } from '../_services/metadata.service'; @Pipe({ name: 'ageRating' }) export class AgeRatingPipe implements PipeTransform { - constructor(private metadataService: MetadataService) {} + constructor() {} transform(value: AgeRating | AgeRatingDto | undefined): Observable { - if (value === undefined || value === null) return of('undefined'); + if (value === undefined || value === null) return of('Unknown'); if (value.hasOwnProperty('title')) { return of((value as AgeRatingDto).title); } - return this.metadataService.getAgeRating((value as AgeRating)); + switch(value) { + case AgeRating.Unknown: return of('Unknown'); + case AgeRating.EarlyChildhood: return of('Early Childhood'); + case AgeRating.AdultsOnly: return of('Adults Only 18+'); + case AgeRating.Everyone: return of('Everyone'); + case AgeRating.Everyone10Plus: return of('Everyone 10+'); + case AgeRating.G: return of('G'); + case AgeRating.KidsToAdults: return of('Kids to Adults'); + case AgeRating.Mature: return of('Mature'); + case AgeRating.Mature17Plus: return of('M'); + case AgeRating.RatingPending: return of('Rating Pending'); + case AgeRating.Teen: return of('Teen'); + case AgeRating.X18Plus: return of('X18+'); + case AgeRating.NotApplicable: return of('Not Applicable'); + } + + return of('Unknown'); } } diff --git a/openapi.json b/openapi.json index 304d3ffdc..58ed0b45a 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.1.28" + "version": "0.7.1.32" }, "servers": [ { @@ -1845,6 +1845,13 @@ "type": "integer", "format": "int32" } + }, + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -1869,6 +1876,13 @@ "type": "integer", "format": "int32" } + }, + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -1893,6 +1907,13 @@ "type": "integer", "format": "int32" } + }, + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -1917,6 +1938,13 @@ "type": "integer", "format": "int32" } + }, + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -1941,6 +1969,13 @@ "type": "integer", "format": "int32" } + }, + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -1965,6 +2000,13 @@ "type": "integer", "format": "int32" } + }, + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -2030,6 +2072,13 @@ "schema": { "type": "string" } + }, + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -3579,6 +3628,13 @@ "type": "integer", "format": "int32" } + }, + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { @@ -3614,6 +3670,14 @@ "format": "int32" } }, + { + "name": "apiKey", + "in": "query", + "description": "User's API Key for authentication", + "schema": { + "type": "string" + } + }, { "name": "extractPdf", "in": "query", @@ -3652,6 +3716,13 @@ "type": "integer", "format": "int32" } + }, + { + "name": "apiKey", + "in": "query", + "schema": { + "type": "string" + } } ], "responses": {