document manga endpoints (#348)

This commit is contained in:
Mitchell Syer
2022-04-24 04:38:33 -04:00
committed by GitHub
parent d82e79b680
commit 047f8c176f
3 changed files with 271 additions and 128 deletions

View File

@@ -53,25 +53,25 @@ object MangaAPI {
path("manga") { path("manga") {
get("{mangaId}", MangaController.retrieve) get("{mangaId}", MangaController.retrieve)
get("{mangaId}/thumbnail", MangaController::thumbnail) get("{mangaId}/thumbnail", MangaController.thumbnail)
get("{mangaId}/category", MangaController::categoryList) get("{mangaId}/category", MangaController.categoryList)
get("{mangaId}/category/{categoryId}", MangaController::addToCategory) get("{mangaId}/category/{categoryId}", MangaController.addToCategory)
delete("{mangaId}/category/{categoryId}", MangaController::removeFromCategory) delete("{mangaId}/category/{categoryId}", MangaController.removeFromCategory)
get("{mangaId}/library", MangaController::addToLibrary) get("{mangaId}/library", MangaController.addToLibrary)
delete("{mangaId}/library", MangaController::removeFromLibrary) delete("{mangaId}/library", MangaController.removeFromLibrary)
patch("{mangaId}/meta", MangaController::meta) patch("{mangaId}/meta", MangaController.meta)
get("{mangaId}/chapters", MangaController::chapterList) get("{mangaId}/chapters", MangaController.chapterList)
get("{mangaId}/chapter/{chapterIndex}", MangaController::chapterRetrieve) get("{mangaId}/chapter/{chapterIndex}", MangaController.chapterRetrieve)
patch("{mangaId}/chapter/{chapterIndex}", MangaController::chapterModify) patch("{mangaId}/chapter/{chapterIndex}", MangaController.chapterModify)
delete("{mangaId}/chapter/{chapterIndex}", MangaController::chapterDelete) delete("{mangaId}/chapter/{chapterIndex}", MangaController.chapterDelete)
patch("{mangaId}/chapter/{chapterIndex}/meta", MangaController::chapterMeta) patch("{mangaId}/chapter/{chapterIndex}/meta", MangaController.chapterMeta)
get("{mangaId}/chapter/{chapterIndex}/page/{index}", MangaController::pageRetrieve) get("{mangaId}/chapter/{chapterIndex}/page/{index}", MangaController.pageRetrieve)
} }
path("category") { path("category") {

View File

@@ -7,7 +7,6 @@ package suwayomi.tachidesk.manga.controller
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import io.javalin.http.Context
import io.javalin.http.HttpCode import io.javalin.http.HttpCode
import suwayomi.tachidesk.manga.impl.CategoryManga import suwayomi.tachidesk.manga.impl.CategoryManga
import suwayomi.tachidesk.manga.impl.Chapter import suwayomi.tachidesk.manga.impl.Chapter
@@ -15,8 +14,11 @@ import suwayomi.tachidesk.manga.impl.Library
import suwayomi.tachidesk.manga.impl.Manga import suwayomi.tachidesk.manga.impl.Manga
import suwayomi.tachidesk.manga.impl.Page import suwayomi.tachidesk.manga.impl.Page
import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReady import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReady
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
import suwayomi.tachidesk.server.JavalinSetup.future import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.util.formParam
import suwayomi.tachidesk.server.util.handler import suwayomi.tachidesk.server.util.handler
import suwayomi.tachidesk.server.util.pathParam import suwayomi.tachidesk.server.util.pathParam
import suwayomi.tachidesk.server.util.queryParam import suwayomi.tachidesk.server.util.queryParam
@@ -30,7 +32,7 @@ object MangaController {
documentWith = { documentWith = {
withOperation { withOperation {
summary("Get a manga") summary("Get a manga")
description("Get a manga from the database using a specific id") description("Get a manga from the database using a specific id.")
} }
}, },
behaviorOf = { ctx, mangaId, onlineFetch -> behaviorOf = { ctx, mangaId, onlineFetch ->
@@ -47,140 +49,278 @@ object MangaController {
) )
/** manga thumbnail */ /** manga thumbnail */
fun thumbnail(ctx: Context) { val thumbnail = handler(
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
val useCache = ctx.queryParam("useCache")?.toBoolean() ?: true queryParam("useCache", true),
documentWith = {
ctx.future( withOperation {
future { Manga.getMangaThumbnail(mangaId, useCache) } summary("Get a manga thumbnail")
.thenApply { description("Get a manga thumbnail from the source or the cache.")
ctx.header("content-type", it.second) }
val httpCacheSeconds = 60 * 60 * 24 },
ctx.header("cache-control", "max-age=$httpCacheSeconds") behaviorOf = { ctx, mangaId, useCache ->
it.first ctx.future(
} future { Manga.getMangaThumbnail(mangaId, useCache) }
) .thenApply {
} ctx.header("content-type", it.second)
val httpCacheSeconds = 60 * 60 * 24
ctx.header("cache-control", "max-age=$httpCacheSeconds")
it.first
}
)
},
withResults = {
mime(HttpCode.OK, "image/*")
httpCode(HttpCode.NOT_FOUND)
}
)
/** adds the manga to library */ /** adds the manga to library */
fun addToLibrary(ctx: Context) { val addToLibrary = handler(
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
documentWith = {
ctx.future( withOperation {
future { Library.addMangaToLibrary(mangaId) } summary("Add manga to library")
) description("Use a manga id to add the manga to your library.\nWill do nothing if manga is already in your library.")
} }
},
behaviorOf = { ctx, mangaId ->
ctx.future(
future { Library.addMangaToLibrary(mangaId) }
)
},
withResults = {
httpCode(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
/** removes the manga from the library */ /** removes the manga from the library */
fun removeFromLibrary(ctx: Context) { val removeFromLibrary = handler(
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
documentWith = {
ctx.future( withOperation {
future { Library.removeMangaFromLibrary(mangaId) } summary("Remove manga to library")
) description("Use a manga id to remove the manga to your library.\nWill do nothing if manga not in your library.")
} }
},
behaviorOf = { ctx, mangaId ->
ctx.future(
future { Library.removeMangaFromLibrary(mangaId) }
)
},
withResults = {
httpCode(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
/** list manga's categories */ /** list manga's categories */
fun categoryList(ctx: Context) { val categoryList = handler(
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
ctx.json(CategoryManga.getMangaCategories(mangaId)) documentWith = {
} withOperation {
summary("Get a manga's categories")
description("Get the list of categories for this manga")
}
},
behaviorOf = { ctx, mangaId ->
ctx.json(CategoryManga.getMangaCategories(mangaId))
},
withResults = {
json<List<CategoryDataClass>>(HttpCode.OK)
}
)
/** adds the manga to category */ /** adds the manga to category */
fun addToCategory(ctx: Context) { val addToCategory = handler(
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
val categoryId = ctx.pathParam("categoryId").toInt() pathParam<Int>("categoryId"),
CategoryManga.addMangaToCategory(mangaId, categoryId) documentWith = {
ctx.status(200) withOperation {
} summary("Add manga to category")
description("Add a manga to a category using their ids.")
}
},
behaviorOf = { ctx, mangaId, categoryId ->
CategoryManga.addMangaToCategory(mangaId, categoryId)
ctx.status(200)
},
withResults = {
httpCode(HttpCode.OK)
}
)
/** removes the manga from the category */ /** removes the manga from the category */
fun removeFromCategory(ctx: Context) { val removeFromCategory = handler(
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
val categoryId = ctx.pathParam("categoryId").toInt() pathParam<Int>("categoryId"),
CategoryManga.removeMangaFromCategory(mangaId, categoryId) documentWith = {
ctx.status(200) withOperation {
} summary("Remove manga from category")
description("Remove a manga from a category using their ids.")
}
},
behaviorOf = { ctx, mangaId, categoryId ->
CategoryManga.removeMangaFromCategory(mangaId, categoryId)
ctx.status(200)
},
withResults = {
httpCode(HttpCode.OK)
}
)
/** used to modify a manga's meta parameters */ /** used to modify a manga's meta parameters */
fun meta(ctx: Context) { val meta = handler(
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
formParam<String>("key"),
val key = ctx.formParam("key")!! formParam<String>("value"),
val value = ctx.formParam("value")!! documentWith = {
withOperation {
Manga.modifyMangaMeta(mangaId, key, value) summary("Add data to manga")
description("A simple Key-Value storage in the manga object, you can set values for whatever you want inside it.")
ctx.status(200) }
} },
behaviorOf = { ctx, mangaId, key, value ->
Manga.modifyMangaMeta(mangaId, key, value)
ctx.status(200)
},
withResults = {
httpCode(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
/** get chapter list when showing a manga */ /** get chapter list when showing a manga */
fun chapterList(ctx: Context) { val chapterList = handler(
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
queryParam("onlineFetch", false),
val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean() ?: false documentWith = {
withOperation {
ctx.future(future { Chapter.getChapterList(mangaId, onlineFetch) }) summary("Get manga chapter list")
} description("Get the manga chapter list from the database or online. If there is no chapters in the database it fetches the chapters online. Use onlineFetch to update chapter list.")
}
},
behaviorOf = { ctx, mangaId, onlineFetch ->
ctx.future(future { Chapter.getChapterList(mangaId, onlineFetch) })
},
withResults = {
json<List<ChapterDataClass>>(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
/** used to display a chapter, get a chapter in order to show its pages */ /** used to display a chapter, get a chapter in order to show its pages */
fun chapterRetrieve(ctx: Context) { val chapterRetrieve = handler(
val chapterIndex = ctx.pathParam("chapterIndex").toInt() pathParam<Int>("mangaId"),
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("chapterIndex"),
ctx.future(future { getChapterDownloadReady(chapterIndex, mangaId) }) documentWith = {
} withOperation {
summary("Get a chapter")
description("Get the chapter from the manga id and chapter index. It will also retrieve the pages for this chapter.")
}
},
behaviorOf = { ctx, mangaId, chapterIndex ->
ctx.future(future { getChapterDownloadReady(chapterIndex, mangaId) })
},
withResults = {
json<ChapterDataClass>(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
/** used to modify a chapter's parameters */ /** used to modify a chapter's parameters */
fun chapterModify(ctx: Context) { val chapterModify = handler(
val chapterIndex = ctx.pathParam("chapterIndex").toInt() pathParam<Int>("mangaId"),
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("chapterIndex"),
formParam<Boolean?>("read"),
formParam<Boolean?>("bookmarked"),
formParam<Boolean?>("markPrevRead"),
formParam<Int?>("lastPageRead"),
documentWith = {
withOperation {
summary("Modify a chapter")
description("Update user info for a given chapter, such as read status, bookmarked, and more.")
}
},
behaviorOf = { ctx, mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead ->
Chapter.modifyChapter(mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead)
val read = ctx.formParam("read")?.toBoolean() ctx.status(200)
val bookmarked = ctx.formParam("bookmarked")?.toBoolean() },
val markPrevRead = ctx.formParam("markPrevRead")?.toBoolean() withResults = {
val lastPageRead = ctx.formParam("lastPageRead")?.toInt() httpCode(HttpCode.OK)
}
Chapter.modifyChapter(mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead) )
ctx.status(200)
}
/** delete a downloaded chapter */ /** delete a downloaded chapter */
fun chapterDelete(ctx: Context) { val chapterDelete = handler(
val chapterIndex = ctx.pathParam("chapterIndex").toInt() pathParam<Int>("mangaId"),
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("chapterIndex"),
documentWith = {
withOperation {
summary("Delete a chapter download")
description("Delete the downloaded chapter and its files.")
}
},
behaviorOf = { ctx, mangaId, chapterIndex ->
Chapter.deleteChapter(mangaId, chapterIndex)
Chapter.deleteChapter(mangaId, chapterIndex) ctx.status(200)
},
ctx.status(200) withResults = {
} httpCode(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
/** used to modify a chapter's meta parameters */ /** used to modify a chapter's meta parameters */
fun chapterMeta(ctx: Context) { val chapterMeta = handler(
val chapterIndex = ctx.pathParam("chapterIndex").toInt() pathParam<Int>("mangaId"),
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("chapterIndex"),
formParam<String>("key"),
formParam<String>("value"),
documentWith = {
withOperation {
summary("Add data to chapter")
description("A simple Key-Value storage in the chapter object, you can set values for whatever you want inside it.")
}
},
behaviorOf = { ctx, mangaId, chapterIndex, key, value ->
Chapter.modifyChapterMeta(mangaId, chapterIndex, key, value)
val key = ctx.formParam("key")!! ctx.status(200)
val value = ctx.formParam("value")!! },
withResults = {
Chapter.modifyChapterMeta(mangaId, chapterIndex, key, value) httpCode(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
ctx.status(200) }
} )
/** get page at index "index" */ /** get page at index "index" */
fun pageRetrieve(ctx: Context) { val pageRetrieve = handler(
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
val chapterIndex = ctx.pathParam("chapterIndex").toInt() pathParam<Int>("chapterIndex"),
val index = ctx.pathParam("index").toInt() pathParam<Int>("index"),
val useCache = ctx.queryParam("useCache")?.toBoolean() ?: true queryParam("useCache", true),
documentWith = {
ctx.future( withOperation {
future { Page.getPageImage(mangaId, chapterIndex, index, useCache) } summary("Get a chapter page")
.thenApply { description("Get a chapter page for a given index. Cache use can be disabled so it only retrieves it directly from the source.")
ctx.header("content-type", it.second) }
it.first },
} behaviorOf = { ctx, mangaId, chapterIndex, index, useCache ->
) ctx.future(
} future { Page.getPageImage(mangaId, chapterIndex, index, useCache) }
.thenApply {
ctx.header("content-type", it.second)
it.first
}
)
},
withResults = {
mime(HttpCode.OK, "image/*")
httpCode(HttpCode.NOT_FOUND)
}
)
} }

View File

@@ -113,7 +113,7 @@ sealed class Param<T> {
} }
class ResultsBuilder { class ResultsBuilder {
val results = mutableListOf<ResultType<*>>() val results = mutableListOf<ResultType>()
inline fun <reified T> json(code: HttpCode) { inline fun <reified T> json(code: HttpCode) {
results += ResultType.MimeType(code, "application/json", T::class.java) results += ResultType.MimeType(code, "application/json", T::class.java)
@@ -121,19 +121,22 @@ class ResultsBuilder {
fun plainText(code: HttpCode) { fun plainText(code: HttpCode) {
results += ResultType.MimeType(code, "text/plain", String::class.java) results += ResultType.MimeType(code, "text/plain", String::class.java)
} }
fun mime(code: HttpCode, mime: String) {
results += ResultType.MimeType(code, mime, null)
}
fun httpCode(code: HttpCode) { fun httpCode(code: HttpCode) {
results += ResultType.StatusCode(code) results += ResultType.StatusCode(code)
} }
} }
sealed class ResultType <T> { sealed class ResultType {
abstract fun applyTo(documentation: OpenApiDocumentation) abstract fun applyTo(documentation: OpenApiDocumentation)
data class MimeType<T>(val code: HttpCode, val mime: String, private val clazz: Class<T>) : ResultType<T>() { data class MimeType(val code: HttpCode, val mime: String, private val clazz: Class<*>?) : ResultType() {
override fun applyTo(documentation: OpenApiDocumentation) { override fun applyTo(documentation: OpenApiDocumentation) {
documentation.result(code.status.toString(), clazz) documentation.result(code.status.toString(), clazz)
} }
} }
data class StatusCode(val code: HttpCode) : ResultType<Unit>() { data class StatusCode(val code: HttpCode) : ResultType() {
override fun applyTo(documentation: OpenApiDocumentation) { override fun applyTo(documentation: OpenApiDocumentation) {
documentation.result<Unit>(code.status.toString()) documentation.result<Unit>(code.status.toString())
} }