document all endpoints (#350)

* Document all endpoints

* Forgot about global endpoints
This commit is contained in:
Mitchell Syer
2022-04-27 07:31:39 -04:00
committed by GitHub
parent 84f701c4ab
commit fe17176b31
9 changed files with 787 additions and 307 deletions

View File

@@ -14,8 +14,8 @@ import suwayomi.tachidesk.global.controller.SettingsController
object GlobalAPI { object GlobalAPI {
fun defineEndpoints() { fun defineEndpoints() {
path("settings") { path("settings") {
get("about", SettingsController::about) get("about", SettingsController.about)
get("check-update", SettingsController::checkUpdate) get("check-update", SettingsController.checkUpdate)
} }
} }
} }

View File

@@ -7,22 +7,48 @@ package suwayomi.tachidesk.global.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 suwayomi.tachidesk.global.impl.About import suwayomi.tachidesk.global.impl.About
import suwayomi.tachidesk.global.impl.AboutDataClass
import suwayomi.tachidesk.global.impl.AppUpdate import suwayomi.tachidesk.global.impl.AppUpdate
import suwayomi.tachidesk.global.impl.UpdateDataClass
import suwayomi.tachidesk.server.JavalinSetup.future import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.util.handler
import suwayomi.tachidesk.server.util.withOperation
/** Settings Page/Screen */ /** Settings Page/Screen */
object SettingsController { object SettingsController {
/** returns some static info about the current app build */ /** returns some static info about the current app build */
fun about(ctx: Context) { val about = handler(
ctx.json(About.getAbout()) documentWith = {
} withOperation {
summary("About Tachidesk")
description("Returns some static info about the current app build")
}
},
behaviorOf = { ctx ->
ctx.json(About.getAbout())
},
withResults = {
json<AboutDataClass>(HttpCode.OK)
}
)
/** check for app updates */ /** check for app updates */
fun checkUpdate(ctx: Context) { val checkUpdate = handler(
ctx.json( documentWith = {
future { AppUpdate.checkUpdate() } withOperation {
) summary("Tachidesk update check")
} description("Check for app updates")
}
},
behaviorOf = { ctx ->
ctx.json(
future { AppUpdate.checkUpdate() }
)
},
withResults = {
json<UpdateDataClass>(HttpCode.OK)
}
)
} }

View File

@@ -24,31 +24,31 @@ import suwayomi.tachidesk.manga.controller.UpdateController
object MangaAPI { object MangaAPI {
fun defineEndpoints() { fun defineEndpoints() {
path("extension") { path("extension") {
get("list", ExtensionController::list) get("list", ExtensionController.list)
get("install/{pkgName}", ExtensionController::install) get("install/{pkgName}", ExtensionController.install)
post("install", ExtensionController::installFile) post("install", ExtensionController.installFile)
get("update/{pkgName}", ExtensionController::update) get("update/{pkgName}", ExtensionController.update)
get("uninstall/{pkgName}", ExtensionController::uninstall) get("uninstall/{pkgName}", ExtensionController.uninstall)
get("icon/{apkName}", ExtensionController::icon) get("icon/{apkName}", ExtensionController.icon)
} }
path("source") { path("source") {
get("list", SourceController::list) get("list", SourceController.list)
get("{sourceId}", SourceController::retrieve) get("{sourceId}", SourceController.retrieve)
get("{sourceId}/popular/{pageNum}", SourceController::popular) get("{sourceId}/popular/{pageNum}", SourceController.popular)
get("{sourceId}/latest/{pageNum}", SourceController::latest) get("{sourceId}/latest/{pageNum}", SourceController.latest)
get("{sourceId}/preferences", SourceController::getPreferences) get("{sourceId}/preferences", SourceController.getPreferences)
post("{sourceId}/preferences", SourceController::setPreference) post("{sourceId}/preferences", SourceController.setPreference)
get("{sourceId}/filters", SourceController::getFilters) get("{sourceId}/filters", SourceController.getFilters)
post("{sourceId}/filters", SourceController::setFilters) post("{sourceId}/filters", SourceController.setFilters)
get("{sourceId}/search", SourceController::searchSingle) get("{sourceId}/search", SourceController.searchSingle)
// get("all/search", SourceController::searchGlobal) // TODO // get("all/search", SourceController.searchGlobal) // TODO
} }
path("manga") { path("manga") {
@@ -75,47 +75,47 @@ object MangaAPI {
} }
path("category") { path("category") {
get("", CategoryController::categoryList) get("", CategoryController.categoryList)
post("", CategoryController::categoryCreate) post("", CategoryController.categoryCreate)
// The order here is important {categoryId} needs to be applied last // The order here is important {categoryId} needs to be applied last
// or throws a NumberFormatException // or throws a NumberFormatException
patch("reorder", CategoryController::categoryReorder) patch("reorder", CategoryController.categoryReorder)
get("{categoryId}", CategoryController::categoryMangas) get("{categoryId}", CategoryController.categoryMangas)
patch("{categoryId}", CategoryController::categoryModify) patch("{categoryId}", CategoryController.categoryModify)
delete("{categoryId}", CategoryController::categoryDelete) delete("{categoryId}", CategoryController.categoryDelete)
} }
path("backup") { path("backup") {
post("import", BackupController::protobufImport) post("import", BackupController.protobufImport)
post("import/file", BackupController::protobufImportFile) post("import/file", BackupController.protobufImportFile)
post("validate", BackupController::protobufValidate) post("validate", BackupController.protobufValidate)
post("validate/file", BackupController::protobufValidateFile) post("validate/file", BackupController.protobufValidateFile)
get("export", BackupController::protobufExport) get("export", BackupController.protobufExport)
get("export/file", BackupController::protobufExportFile) get("export/file", BackupController.protobufExportFile)
} }
path("downloads") { path("downloads") {
ws("", DownloadController::downloadsWS) ws("", DownloadController::downloadsWS)
get("start", DownloadController::start) get("start", DownloadController.start)
get("stop", DownloadController::stop) get("stop", DownloadController.stop)
get("clear", DownloadController::stop) get("clear", DownloadController.stop)
} }
path("download") { path("download") {
get("{mangaId}/chapter/{chapterIndex}", DownloadController::queueChapter) get("{mangaId}/chapter/{chapterIndex}", DownloadController.queueChapter)
delete("{mangaId}/chapter/{chapterIndex}", DownloadController::unqueueChapter) delete("{mangaId}/chapter/{chapterIndex}", DownloadController.unqueueChapter)
} }
path("update") { path("update") {
get("recentChapters/{pageNum}", UpdateController::recentChapters) get("recentChapters/{pageNum}", UpdateController.recentChapters)
post("fetch", UpdateController::categoryUpdate) post("fetch", UpdateController.categoryUpdate)
post("reset", UpdateController.reset) post("reset", UpdateController.reset)
get("summary", UpdateController::updateSummary) get("summary", UpdateController.updateSummary)
ws("", UpdateController::categoryUpdateWS) ws("", UpdateController::categoryUpdateWS)
} }
} }

View File

@@ -1,11 +1,14 @@
package suwayomi.tachidesk.manga.controller package suwayomi.tachidesk.manga.controller
import io.javalin.http.Context import io.javalin.http.HttpCode
import suwayomi.tachidesk.manga.impl.backup.AbstractBackupValidator
import suwayomi.tachidesk.manga.impl.backup.BackupFlags import suwayomi.tachidesk.manga.impl.backup.BackupFlags
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupExport import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupExport
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupImport import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupImport
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator
import suwayomi.tachidesk.server.JavalinSetup.future import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.util.handler
import suwayomi.tachidesk.server.util.withOperation
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@@ -19,78 +22,156 @@ import java.util.Date
object BackupController { object BackupController {
/** expects a Tachiyomi protobuf backup in the body */ /** expects a Tachiyomi protobuf backup in the body */
fun protobufImport(ctx: Context) { val protobufImport = handler(
ctx.future( documentWith = {
future { withOperation {
ProtoBackupImport.performRestore(ctx.bodyAsInputStream()) summary("Restore a backup")
description("Expects a Tachiyomi protobuf backup in the body")
} }
) },
} behaviorOf = { ctx ->
ctx.future(
future {
ProtoBackupImport.performRestore(ctx.bodyAsInputStream())
}
)
},
withResults = {
httpCode(HttpCode.OK)
}
)
/** expects a Tachiyomi protobuf backup as a file upload, the file must be named "backup.proto.gz" */ /** expects a Tachiyomi protobuf backup as a file upload, the file must be named "backup.proto.gz" */
fun protobufImportFile(ctx: Context) { val protobufImportFile = handler(
// TODO: rewrite this with ctx.uploadedFiles(), don't call the multipart field "backup.proto.gz" documentWith = {
ctx.future( withOperation {
future { summary("Restore a backup file")
ProtoBackupImport.performRestore(ctx.uploadedFile("backup.proto.gz")!!.content) description("Expects a Tachiyomi protobuf backup as a file upload, the file must be named \"backup.proto.gz\"")
} }
) uploadedFile("backup.proto.gz") {
} it.description("Protobuf backup")
it.required(true)
}
},
behaviorOf = { ctx ->
// TODO: rewrite this with ctx.uploadedFiles(), don't call the multipart field "backup.proto.gz"
ctx.future(
future {
ProtoBackupImport.performRestore(ctx.uploadedFile("backup.proto.gz")!!.content)
}
)
},
withResults = {
httpCode(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
/** returns a Tachiyomi protobuf backup created from the current database as a body */ /** returns a Tachiyomi protobuf backup created from the current database as a body */
fun protobufExport(ctx: Context) { val protobufExport = handler(
ctx.contentType("application/octet-stream") documentWith = {
ctx.future( withOperation {
future { summary("Create a backup")
ProtoBackupExport.createBackup( description("Returns a Tachiyomi protobuf backup created from the current database as a body")
BackupFlags(
includeManga = true,
includeCategories = true,
includeChapters = true,
includeTracking = true,
includeHistory = true,
)
)
} }
) },
} behaviorOf = { ctx ->
ctx.contentType("application/octet-stream")
ctx.future(
future {
ProtoBackupExport.createBackup(
BackupFlags(
includeManga = true,
includeCategories = true,
includeChapters = true,
includeTracking = true,
includeHistory = true,
)
)
}
)
},
withResults = {
mime(HttpCode.OK, "application/octet-stream")
}
)
/** returns a Tachiyomi protobuf backup created from the current database as a file */ /** returns a Tachiyomi protobuf backup created from the current database as a file */
fun protobufExportFile(ctx: Context) { val protobufExportFile = handler(
ctx.contentType("application/octet-stream") documentWith = {
val currentDate = SimpleDateFormat("yyyy-MM-dd_HH-mm").format(Date()) withOperation {
summary("Create a backup file")
ctx.header("Content-Disposition", """attachment; filename="tachidesk_$currentDate.proto.gz"""") description("Returns a Tachiyomi protobuf backup created from the current database as a file")
ctx.future(
future {
ProtoBackupExport.createBackup(
BackupFlags(
includeManga = true,
includeCategories = true,
includeChapters = true,
includeTracking = true,
includeHistory = true,
)
)
} }
) },
} behaviorOf = { ctx ->
ctx.contentType("application/octet-stream")
val currentDate = SimpleDateFormat("yyyy-MM-dd_HH-mm").format(Date())
ctx.header("Content-Disposition", """attachment; filename="tachidesk_$currentDate.proto.gz"""")
ctx.future(
future {
ProtoBackupExport.createBackup(
BackupFlags(
includeManga = true,
includeCategories = true,
includeChapters = true,
includeTracking = true,
includeHistory = true,
)
)
}
)
},
withResults = {
mime(HttpCode.OK, "application/octet-stream")
}
)
/** Reports missing sources and trackers, expects a Tachiyomi protobuf backup in the body */ /** Reports missing sources and trackers, expects a Tachiyomi protobuf backup in the body */
fun protobufValidate(ctx: Context) { val protobufValidate = handler(
ctx.future( documentWith = {
future { withOperation {
ProtoBackupValidator.validate(ctx.bodyAsInputStream()) summary("Validate a backup")
description("Reports missing sources and trackers, expects a Tachiyomi protobuf backup in the body")
} }
) body<ByteArray>("") {
}
}
},
behaviorOf = { ctx ->
ctx.future(
future {
ProtoBackupValidator.validate(ctx.bodyAsInputStream())
}
)
},
withResults = {
json<AbstractBackupValidator.ValidationResult>(HttpCode.OK)
}
)
/** Reports missing sources and trackers, expects a Tachiyomi protobuf backup as a file upload, the file must be named "backup.proto.gz" */ /** Reports missing sources and trackers, expects a Tachiyomi protobuf backup as a file upload, the file must be named "backup.proto.gz" */
fun protobufValidateFile(ctx: Context) { val protobufValidateFile = handler(
ctx.future( documentWith = {
future { withOperation {
ProtoBackupValidator.validate(ctx.uploadedFile("backup.proto.gz")!!.content) summary("Validate a backup file")
description("Reports missing sources and trackers, expects a Tachiyomi protobuf backup as a file upload, the file must be named \"backup.proto.gz\"")
} }
) uploadedFile("backup.proto.gz") {
} it.description("Protobuf backup")
it.required(true)
}
},
behaviorOf = { ctx ->
ctx.future(
future {
ProtoBackupValidator.validate(ctx.uploadedFile("backup.proto.gz")!!.content)
}
)
},
withResults = {
json<AbstractBackupValidator.ValidationResult>(HttpCode.OK)
}
)
} }

View File

@@ -7,50 +7,127 @@ 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 suwayomi.tachidesk.manga.impl.Category import suwayomi.tachidesk.manga.impl.Category
import suwayomi.tachidesk.manga.impl.CategoryManga import suwayomi.tachidesk.manga.impl.CategoryManga
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
import suwayomi.tachidesk.server.util.formParam
import suwayomi.tachidesk.server.util.handler
import suwayomi.tachidesk.server.util.pathParam
import suwayomi.tachidesk.server.util.withOperation
object CategoryController { object CategoryController {
/** category list */ /** category list */
fun categoryList(ctx: Context) { val categoryList = handler(
ctx.json(Category.getCategoryList()) documentWith = {
} withOperation {
summary("Category list")
description("get a list of categories")
}
},
behaviorOf = { ctx ->
ctx.json(Category.getCategoryList())
},
withResults = {
json<List<CategoryDataClass>>(HttpCode.OK)
}
)
/** category create */ /** category create */
fun categoryCreate(ctx: Context) { val categoryCreate = handler(
val name = ctx.formParam("name")!! formParam<String>("name"),
Category.createCategory(name) documentWith = {
ctx.status(200) withOperation {
} summary("Category create")
description("Create a category")
}
},
behaviorOf = { ctx, name ->
if (Category.createCategory(name) != -1) {
ctx.status(200)
} else {
ctx.status(HttpCode.BAD_REQUEST)
}
},
withResults = {
httpCode(HttpCode.OK)
httpCode(HttpCode.BAD_REQUEST)
}
)
/** category modification */ /** category modification */
fun categoryModify(ctx: Context) { val categoryModify = handler(
val categoryId = ctx.pathParam("categoryId").toInt() pathParam<Int>("categoryId"),
val name = ctx.formParam("name") formParam<String?>("name"),
val isDefault = ctx.formParam("default")?.toBoolean() formParam<Boolean?>("default"),
Category.updateCategory(categoryId, name, isDefault) documentWith = {
ctx.status(200) withOperation {
} summary("Category modify")
description("Modify a category")
}
},
behaviorOf = { ctx, categoryId, name, isDefault ->
Category.updateCategory(categoryId, name, isDefault)
ctx.status(200)
},
withResults = {
httpCode(HttpCode.OK)
}
)
/** category delete */ /** category delete */
fun categoryDelete(ctx: Context) { val categoryDelete = handler(
val categoryId = ctx.pathParam("categoryId").toInt() pathParam<Int>("categoryId"),
Category.removeCategory(categoryId) documentWith = {
ctx.status(200) withOperation {
} summary("Category delete")
description("Delete a category")
}
},
behaviorOf = { ctx, categoryId ->
Category.removeCategory(categoryId)
ctx.status(200)
},
withResults = {
httpCode(HttpCode.OK)
}
)
/** returns the manga list associated with a category */ /** returns the manga list associated with a category */
fun categoryMangas(ctx: Context) { val categoryMangas = handler(
val categoryId = ctx.pathParam("categoryId").toInt() pathParam<Int>("categoryId"),
ctx.json(CategoryManga.getCategoryMangaList(categoryId)) documentWith = {
} withOperation {
summary("Category manga")
description("Returns the manga list associated with a category")
}
},
behaviorOf = { ctx, categoryId ->
ctx.json(CategoryManga.getCategoryMangaList(categoryId))
},
withResults = {
json<List<MangaDataClass>>(HttpCode.OK)
}
)
/** category re-ordering */ /** category re-ordering */
fun categoryReorder(ctx: Context) { val categoryReorder = handler(
val from = ctx.formParam("from")!!.toInt() formParam<Int>("from"),
val to = ctx.formParam("to")!!.toInt() formParam<Int>("to"),
Category.reorderCategory(from, to) documentWith = {
ctx.status(200) withOperation {
} summary("Category re-ordering")
description("Re-order a category")
}
},
behaviorOf = { ctx, from, to ->
Category.reorderCategory(from, to)
ctx.status(200)
},
withResults = {
httpCode(HttpCode.OK)
}
)
} }

View File

@@ -7,10 +7,13 @@ 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.websocket.WsConfig import io.javalin.websocket.WsConfig
import suwayomi.tachidesk.manga.impl.download.DownloadManager import suwayomi.tachidesk.manga.impl.download.DownloadManager
import suwayomi.tachidesk.server.JavalinSetup.future import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.util.handler
import suwayomi.tachidesk.server.util.pathParam
import suwayomi.tachidesk.server.util.withOperation
object DownloadController { object DownloadController {
/** Download queue stats */ /** Download queue stats */
@@ -28,45 +31,100 @@ object DownloadController {
} }
/** Start the downloader */ /** Start the downloader */
fun start(ctx: Context) { val start = handler(
DownloadManager.start() documentWith = {
withOperation {
summary("Downloader start")
description("Start the downloader")
}
},
behaviorOf = { ctx ->
DownloadManager.start()
ctx.status(200) ctx.status(200)
} },
withResults = {
httpCode(HttpCode.OK)
}
)
/** Stop the downloader */ /** Stop the downloader */
fun stop(ctx: Context) { val stop = handler(
DownloadManager.stop() documentWith = {
withOperation {
summary("Downloader stop")
description("Stop the downloader")
}
},
behaviorOf = { ctx ->
DownloadManager.stop()
ctx.status(200) ctx.status(200)
} },
withResults = {
httpCode(HttpCode.OK)
}
)
/** clear download queue */ /** clear download queue */
fun clear(ctx: Context) { val clear = handler(
DownloadManager.clear() documentWith = {
withOperation {
summary("Downloader clear")
description("Clear download queue")
}
},
behaviorOf = { ctx ->
DownloadManager.clear()
ctx.status(200) ctx.status(200)
}
},
withResults = {
httpCode(HttpCode.OK)
}
)
/** Queue chapter for download */ /** Queue chapter for download */
fun queueChapter(ctx: Context) { val queueChapter = handler(
val chapterIndex = ctx.pathParam("chapterIndex").toInt() pathParam<Int>("chapterIndex"),
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
documentWith = {
ctx.future( withOperation {
future { summary("Downloader add chapter")
DownloadManager.enqueue(chapterIndex, mangaId) description("Queue chapter for download")
} }
) },
} behaviorOf = { ctx, chapterIndex, mangaId ->
ctx.future(
future {
DownloadManager.enqueue(chapterIndex, mangaId)
}
)
},
withResults = {
httpCode(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
/** delete chapter from download queue */ /** delete chapter from download queue */
fun unqueueChapter(ctx: Context) { val unqueueChapter = handler(
val chapterIndex = ctx.pathParam("chapterIndex").toInt() pathParam<Int>("chapterIndex"),
val mangaId = ctx.pathParam("mangaId").toInt() pathParam<Int>("mangaId"),
documentWith = {
withOperation {
summary("Downloader remove chapter")
description("Delete chapter from download queue")
}
},
behaviorOf = { ctx, chapterIndex, mangaId ->
DownloadManager.unqueue(chapterIndex, mangaId)
DownloadManager.unqueue(chapterIndex, mangaId) ctx.status(200)
},
ctx.status(200) withResults = {
} httpCode(HttpCode.OK)
}
)
} }

View File

@@ -7,78 +7,160 @@ 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 mu.KotlinLogging import mu.KotlinLogging
import suwayomi.tachidesk.manga.impl.extension.Extension import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList import suwayomi.tachidesk.manga.impl.extension.ExtensionsList
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
import suwayomi.tachidesk.server.JavalinSetup.future import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.util.handler
import suwayomi.tachidesk.server.util.pathParam
import suwayomi.tachidesk.server.util.queryParam
import suwayomi.tachidesk.server.util.withOperation
object ExtensionController { object ExtensionController {
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
/** list all extensions */ /** list all extensions */
fun list(ctx: Context) { val list = handler(
ctx.future( documentWith = {
future { withOperation {
ExtensionsList.getExtensionList() summary("Extension list")
description("List all extensions")
} }
) },
} behaviorOf = { ctx ->
ctx.future(
future {
ExtensionsList.getExtensionList()
}
)
},
withResults = {
json<List<ExtensionDataClass>>(HttpCode.OK)
}
)
/** install extension identified with "pkgName" */ /** install extension identified with "pkgName" */
fun install(ctx: Context) { val install = handler(
val pkgName = ctx.pathParam("pkgName") pathParam<String>("pkgName"),
documentWith = {
ctx.future( withOperation {
future { summary("Extension install")
Extension.installExtension(pkgName) description("install extension identified with \"pkgName\"")
} }
) },
} behaviorOf = { ctx, pkgName ->
ctx.future(
future {
Extension.installExtension(pkgName)
}
)
},
withResults = {
httpCode(HttpCode.CREATED)
httpCode(HttpCode.FOUND)
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
}
)
/** install the uploaded apk file */ /** install the uploaded apk file */
fun installFile(ctx: Context) { val installFile = handler(
documentWith = {
val uploadedFile = ctx.uploadedFile("file")!! withOperation {
logger.debug { "Uploaded extension file name: " + uploadedFile.filename } summary("Extension install apk")
description("Install the uploaded apk file")
ctx.future(
future {
Extension.installExternalExtension(uploadedFile.content, uploadedFile.filename)
} }
) uploadedFile("file") {
} it.description("Extension apk")
it.required(true)
}
},
behaviorOf = { ctx ->
val uploadedFile = ctx.uploadedFile("file")!!
logger.debug { "Uploaded extension file name: " + uploadedFile.filename }
ctx.future(
future {
Extension.installExternalExtension(uploadedFile.content, uploadedFile.filename)
}
)
},
withResults = {
httpCode(HttpCode.CREATED)
httpCode(HttpCode.FOUND)
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
}
)
/** update extension identified with "pkgName" */ /** update extension identified with "pkgName" */
fun update(ctx: Context) { val update = handler(
val pkgName = ctx.pathParam("pkgName") pathParam<String>("pkgName"),
documentWith = {
ctx.future( withOperation {
future { summary("Extension update")
Extension.updateExtension(pkgName) description("Update extension identified with \"pkgName\"")
} }
) },
} behaviorOf = { ctx, pkgName ->
ctx.future(
future {
Extension.updateExtension(pkgName)
}
)
},
withResults = {
httpCode(HttpCode.CREATED)
httpCode(HttpCode.FOUND)
httpCode(HttpCode.NOT_FOUND)
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
}
)
/** uninstall extension identified with "pkgName" */ /** uninstall extension identified with "pkgName" */
fun uninstall(ctx: Context) { val uninstall = handler(
val pkgName = ctx.pathParam("pkgName") pathParam<String>("pkgName"),
documentWith = {
Extension.uninstallExtension(pkgName) withOperation {
ctx.status(200) summary("Extension uninstall")
} description("Uninstall extension identified with \"pkgName\"")
}
},
behaviorOf = { ctx, pkgName ->
Extension.uninstallExtension(pkgName)
ctx.status(200)
},
withResults = {
httpCode(HttpCode.CREATED)
httpCode(HttpCode.FOUND)
httpCode(HttpCode.NOT_FOUND)
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
}
)
/** icon for extension named `apkName` */ /** icon for extension named `apkName` */
fun icon(ctx: Context) { val icon = handler(
val apkName = ctx.pathParam("apkName") pathParam<String>("apkName"),
val useCache = ctx.queryParam("useCache")?.toBoolean() ?: true queryParam("useCache", true),
documentWith = {
ctx.future( withOperation {
future { Extension.getExtensionIcon(apkName, useCache) } summary("Extension icon")
.thenApply { description("Icon for extension named `apkName`")
ctx.header("content-type", it.second) }
it.first },
} behaviorOf = { ctx, apkName, useCache ->
) ctx.future(
} future { Extension.getExtensionIcon(apkName, useCache) }
.thenApply {
ctx.header("content-type", it.second)
it.first
}
)
},
withResults = {
httpCode(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
} }

View File

@@ -7,7 +7,7 @@ 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 kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.kodein.di.DI import org.kodein.di.DI
@@ -18,87 +18,205 @@ import suwayomi.tachidesk.manga.impl.Search
import suwayomi.tachidesk.manga.impl.Search.FilterChange import suwayomi.tachidesk.manga.impl.Search.FilterChange
import suwayomi.tachidesk.manga.impl.Source import suwayomi.tachidesk.manga.impl.Source
import suwayomi.tachidesk.manga.impl.Source.SourcePreferenceChange import suwayomi.tachidesk.manga.impl.Source.SourcePreferenceChange
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
import suwayomi.tachidesk.server.JavalinSetup.future import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.util.handler
import suwayomi.tachidesk.server.util.pathParam
import suwayomi.tachidesk.server.util.queryParam
import suwayomi.tachidesk.server.util.withOperation
import javax.sound.sampled.SourceDataLine
object SourceController { object SourceController {
/** list of sources */ /** list of sources */
fun list(ctx: Context) { val list = handler(
ctx.json(Source.getSourceList()) documentWith = {
} withOperation {
summary("Sources list")
description("List of sources")
}
},
behaviorOf = { ctx ->
ctx.json(Source.getSourceList())
},
withResults = {
json<List<SourceDataLine>>(HttpCode.OK)
}
)
/** fetch source with id `sourceId` */ /** fetch source with id `sourceId` */
fun retrieve(ctx: Context) { val retrieve = handler(
val sourceId = ctx.pathParam("sourceId").toLong() pathParam<Long>("sourceId"),
ctx.json(Source.getSource(sourceId)!!) documentWith = {
} withOperation {
summary("Source fetch")
description("Fetch source with id `sourceId`")
}
},
behaviorOf = { ctx, sourceId ->
ctx.json(Source.getSource(sourceId)!!)
},
withResults = {
json<SourceDataLine>(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
}
)
/** popular mangas from source with id `sourceId` */ /** popular mangas from source with id `sourceId` */
fun popular(ctx: Context) { val popular = handler(
val sourceId = ctx.pathParam("sourceId").toLong() pathParam<Long>("sourceId"),
val pageNum = ctx.pathParam("pageNum").toInt() pathParam<Int>("pageNum"),
ctx.future( documentWith = {
future { withOperation {
MangaList.getMangaList(sourceId, pageNum, popular = true) summary("Source popular manga")
description("Popular mangas from source with id `sourceId`")
} }
) },
} behaviorOf = { ctx, sourceId, pageNum ->
ctx.future(
future {
MangaList.getMangaList(sourceId, pageNum, popular = true)
}
)
},
withResults = {
json<PagedMangaListDataClass>(HttpCode.OK)
}
)
/** latest mangas from source with id `sourceId` */ /** latest mangas from source with id `sourceId` */
fun latest(ctx: Context) { val latest = handler(
val sourceId = ctx.pathParam("sourceId").toLong() pathParam<Long>("sourceId"),
val pageNum = ctx.pathParam("pageNum").toInt() pathParam<Int>("pageNum"),
ctx.future( documentWith = {
future { withOperation {
MangaList.getMangaList(sourceId, pageNum, popular = false) summary("Source latest manga")
description("Latest mangas from source with id `sourceId`")
} }
) },
} behaviorOf = { ctx, sourceId, pageNum ->
ctx.future(
future {
MangaList.getMangaList(sourceId, pageNum, popular = false)
}
)
},
withResults = {
json<PagedMangaListDataClass>(HttpCode.OK)
}
)
/** fetch preferences of source with id `sourceId` */ /** fetch preferences of source with id `sourceId` */
fun getPreferences(ctx: Context) { val getPreferences = handler(
val sourceId = ctx.pathParam("sourceId").toLong() pathParam<Long>("sourceId"),
ctx.json(Source.getSourcePreferences(sourceId)) documentWith = {
} withOperation {
summary("Source preferences")
description("Fetch preferences of source with id `sourceId`")
}
},
behaviorOf = { ctx, sourceId ->
ctx.json(Source.getSourcePreferences(sourceId))
},
withResults = {
json<List<Source.PreferenceObject>>(HttpCode.OK)
}
)
/** set one preference of source with id `sourceId` */ /** set one preference of source with id `sourceId` */
fun setPreference(ctx: Context) { val setPreference = handler(
val sourceId = ctx.pathParam("sourceId").toLong() pathParam<Long>("sourceId"),
val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java) documentWith = {
ctx.json(Source.setSourcePreference(sourceId, preferenceChange)) withOperation {
} summary("Source preference set")
description("Set one preference of source with id `sourceId`")
}
},
behaviorOf = { ctx, sourceId ->
val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java)
ctx.json(Source.setSourcePreference(sourceId, preferenceChange))
},
withResults = {
httpCode(HttpCode.OK)
}
)
/** fetch filters of source with id `sourceId` */ /** fetch filters of source with id `sourceId` */
fun getFilters(ctx: Context) { val getFilters = handler(
val sourceId = ctx.pathParam("sourceId").toLong() pathParam<Long>("sourceId"),
val reset = ctx.queryParam("reset")?.toBoolean() ?: false queryParam("reset", false),
ctx.json(Search.getFilterList(sourceId, reset)) documentWith = {
} withOperation {
summary("Source filters")
description("Fetch filters of source with id `sourceId`")
}
},
behaviorOf = { ctx, sourceId, reset ->
ctx.json(Search.getFilterList(sourceId, reset))
},
withResults = {
json<List<Search.FilterObject>>(HttpCode.OK)
}
)
private val json by DI.global.instance<Json>() private val json by DI.global.instance<Json>()
/** change filters of source with id `sourceId` */ /** change filters of source with id `sourceId` */
fun setFilters(ctx: Context) { val setFilters = handler(
val sourceId = ctx.pathParam("sourceId").toLong() pathParam<Long>("sourceId"),
val filterChange = try { documentWith = {
json.decodeFromString<List<FilterChange>>(ctx.body()) withOperation {
} catch (e: Exception) { summary("Source filters set")
listOf(json.decodeFromString<FilterChange>(ctx.body())) description("Change filters of source with id `sourceId`")
} }
},
behaviorOf = { ctx, sourceId ->
val filterChange = try {
json.decodeFromString<List<FilterChange>>(ctx.body())
} catch (e: Exception) {
listOf(json.decodeFromString<FilterChange>(ctx.body()))
}
ctx.json(Search.setFilter(sourceId, filterChange)) ctx.json(Search.setFilter(sourceId, filterChange))
} },
withResults = {
httpCode(HttpCode.OK)
}
)
/** single source search */ /** single source search */
fun searchSingle(ctx: Context) { val searchSingle = handler(
val sourceId = ctx.pathParam("sourceId").toLong() pathParam<Long>("sourceId"),
val searchTerm = ctx.queryParam("searchTerm") ?: "" queryParam("searchTerm", ""),
val pageNum = ctx.queryParam("pageNum")?.toInt() ?: 1 queryParam("pageNum", 1),
ctx.future(future { Search.sourceSearch(sourceId, searchTerm, pageNum) }) documentWith = {
} withOperation {
summary("Source search")
description("Single source search")
}
},
behaviorOf = { ctx, sourceId, searchTerm, pageNum ->
ctx.future(future { Search.sourceSearch(sourceId, searchTerm, pageNum) })
},
withResults = {
json<PagedMangaListDataClass>(HttpCode.OK)
}
)
/** all source search */ /** all source search */
fun searchAll(ctx: Context) { // TODO val searchAll = handler(
val searchTerm = ctx.pathParam("searchTerm") pathParam<String>("searchTerm"),
ctx.json(Search.sourceGlobalSearch(searchTerm)) documentWith = {
} withOperation {
summary("Source global search")
description("All source search")
}
},
behaviorOf = { ctx, searchTerm -> // TODO
ctx.json(Search.sourceGlobalSearch(searchTerm))
},
withResults = {
httpCode(HttpCode.OK)
}
)
} }

View File

@@ -1,6 +1,5 @@
package suwayomi.tachidesk.manga.controller package suwayomi.tachidesk.manga.controller
import io.javalin.http.Context
import io.javalin.http.HttpCode import io.javalin.http.HttpCode
import io.javalin.websocket.WsConfig import io.javalin.websocket.WsConfig
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -12,10 +11,15 @@ import suwayomi.tachidesk.manga.impl.Category
import suwayomi.tachidesk.manga.impl.CategoryManga import suwayomi.tachidesk.manga.impl.CategoryManga
import suwayomi.tachidesk.manga.impl.Chapter import suwayomi.tachidesk.manga.impl.Chapter
import suwayomi.tachidesk.manga.impl.update.IUpdater import suwayomi.tachidesk.manga.impl.update.IUpdater
import suwayomi.tachidesk.manga.impl.update.UpdateStatus
import suwayomi.tachidesk.manga.impl.update.UpdaterSocket import suwayomi.tachidesk.manga.impl.update.UpdaterSocket
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
import suwayomi.tachidesk.manga.model.dataclass.PaginatedList
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.withOperation import suwayomi.tachidesk.server.util.withOperation
/* /*
@@ -29,35 +33,57 @@ object UpdateController {
private val logger = KotlinLogging.logger { } private val logger = KotlinLogging.logger { }
/** get recently updated manga chapters */ /** get recently updated manga chapters */
fun recentChapters(ctx: Context) { val recentChapters = handler(
val pageNum = ctx.pathParam("pageNum").toInt() pathParam<Int>("pageNum"),
documentWith = {
ctx.future( withOperation {
future { summary("Updates fetch")
Chapter.getRecentChapters(pageNum) description("Get recently updated manga chapters")
}
)
}
fun categoryUpdate(ctx: Context) {
val categoryId = ctx.formParam("category")?.toIntOrNull()
val categoriesForUpdate = ArrayList<CategoryDataClass>()
if (categoryId == null) {
logger.info { "Adding Library to Update Queue" }
categoriesForUpdate.addAll(Category.getCategoryList())
} else {
val category = Category.getCategoryById(categoryId)
if (category != null) {
categoriesForUpdate.add(category)
} else {
logger.info { "No Category found" }
ctx.status(HttpCode.BAD_REQUEST)
return
} }
},
behaviorOf = { ctx, pageNum ->
ctx.future(
future {
Chapter.getRecentChapters(pageNum)
}
)
},
withResults = {
json<PaginatedList<MangaDataClass>>(HttpCode.OK)
} }
addCategoriesToUpdateQueue(categoriesForUpdate, true) )
ctx.status(HttpCode.OK)
} val categoryUpdate = handler(
formParam<Int?>("categoryId"),
documentWith = {
withOperation {
summary("Updater start")
description("Starts the updater")
}
},
behaviorOf = { ctx, categoryId ->
val categoriesForUpdate = ArrayList<CategoryDataClass>()
if (categoryId == null) {
logger.info { "Adding Library to Update Queue" }
categoriesForUpdate.addAll(Category.getCategoryList())
} else {
val category = Category.getCategoryById(categoryId)
if (category != null) {
categoriesForUpdate.add(category)
} else {
logger.info { "No Category found" }
ctx.status(HttpCode.BAD_REQUEST)
return@handler
}
}
addCategoriesToUpdateQueue(categoriesForUpdate, true)
ctx.status(HttpCode.OK)
},
withResults = {
httpCode(HttpCode.OK)
httpCode(HttpCode.BAD_REQUEST)
}
)
private fun addCategoriesToUpdateQueue(categories: List<CategoryDataClass>, clear: Boolean = false) { private fun addCategoriesToUpdateQueue(categories: List<CategoryDataClass>, clear: Boolean = false) {
val updater by DI.global.instance<IUpdater>() val updater by DI.global.instance<IUpdater>()
@@ -84,15 +110,27 @@ object UpdateController {
} }
} }
fun updateSummary(ctx: Context) { val updateSummary = handler(
val updater by DI.global.instance<IUpdater>() documentWith = {
ctx.json(updater.getStatus().value.getJsonSummary()) withOperation {
} summary("Updater summary")
description("Gets the latest updater summary")
}
},
behaviorOf = { ctx ->
val updater by DI.global.instance<IUpdater>()
ctx.json(updater.getStatus().value.getJsonSummary())
},
withResults = {
json<UpdateStatus>(HttpCode.OK)
}
)
val reset = handler( val reset = handler(
documentWith = { documentWith = {
withOperation { withOperation {
summary("Stops and resets the Updater") summary("Updater reset")
description("Stops and resets the Updater")
} }
}, },
behaviorOf = { ctx -> behaviorOf = { ctx ->