mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-03 10:54:38 -05:00
Add batch chapter update endpoint (#442)
This commit is contained in:
@@ -66,6 +66,7 @@ object MangaAPI {
|
|||||||
patch("{mangaId}/meta", MangaController.meta)
|
patch("{mangaId}/meta", MangaController.meta)
|
||||||
|
|
||||||
get("{mangaId}/chapters", MangaController.chapterList)
|
get("{mangaId}/chapters", MangaController.chapterList)
|
||||||
|
post("{mangaId}/chapter/batch", MangaController.chapterBatch)
|
||||||
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)
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ package suwayomi.tachidesk.manga.controller
|
|||||||
* 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.HttpCode
|
import io.javalin.http.HttpCode
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.kodein.di.DI
|
||||||
|
import org.kodein.di.conf.global
|
||||||
|
import org.kodein.di.instance
|
||||||
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.Library
|
import suwayomi.tachidesk.manga.impl.Library
|
||||||
@@ -26,6 +31,8 @@ import suwayomi.tachidesk.server.util.withOperation
|
|||||||
import kotlin.time.Duration.Companion.days
|
import kotlin.time.Duration.Companion.days
|
||||||
|
|
||||||
object MangaController {
|
object MangaController {
|
||||||
|
private val json by DI.global.instance<Json>()
|
||||||
|
|
||||||
/** get manga info */
|
/** get manga info */
|
||||||
val retrieve = handler(
|
val retrieve = handler(
|
||||||
pathParam<Int>("mangaId"),
|
pathParam<Int>("mangaId"),
|
||||||
@@ -211,6 +218,25 @@ object MangaController {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** batch edit chapters */
|
||||||
|
val chapterBatch = handler(
|
||||||
|
pathParam<Int>("mangaId"),
|
||||||
|
documentWith = {
|
||||||
|
withOperation {
|
||||||
|
summary("Chapters update multiple")
|
||||||
|
description("Update multiple chapters. For batch marking as read, or bookmarking")
|
||||||
|
}
|
||||||
|
body<Chapter.ChapterBatchEditInput>()
|
||||||
|
},
|
||||||
|
behaviorOf = { ctx, mangaId ->
|
||||||
|
val input = json.decodeFromString<Chapter.ChapterBatchEditInput>(ctx.body())
|
||||||
|
Chapter.modifyChapters(input, mangaId)
|
||||||
|
},
|
||||||
|
withResults = {
|
||||||
|
httpCode(HttpCode.OK)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/** 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 */
|
||||||
val chapterRetrieve = handler(
|
val chapterRetrieve = handler(
|
||||||
pathParam<Int>("mangaId"),
|
pathParam<Int>("mangaId"),
|
||||||
|
|||||||
@@ -10,16 +10,12 @@ package suwayomi.tachidesk.manga.impl
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
|
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
import org.jetbrains.exposed.sql.SortOrder
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.SortOrder.ASC
|
import org.jetbrains.exposed.sql.SortOrder.ASC
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
import org.jetbrains.exposed.sql.and
|
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
|
||||||
import org.jetbrains.exposed.sql.insert
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
|
||||||
import suwayomi.tachidesk.manga.impl.Manga.getManga
|
import suwayomi.tachidesk.manga.impl.Manga.getManga
|
||||||
import suwayomi.tachidesk.manga.impl.util.getChapterDir
|
import suwayomi.tachidesk.manga.impl.util.getChapterDir
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||||
@@ -198,6 +194,52 @@ object Chapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChapterChange(
|
||||||
|
val isRead: Boolean? = null,
|
||||||
|
val isBookmarked: Boolean? = null,
|
||||||
|
val lastPageRead: Int? = null // this probably won't be very useful, but for completion's sake
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChapterBatchEditInput(
|
||||||
|
val chapterIds: List<Int>? = null,
|
||||||
|
val chapterIndexes: List<Int>? = null,
|
||||||
|
val change: ChapterChange?
|
||||||
|
)
|
||||||
|
|
||||||
|
fun modifyChapters(input: ChapterBatchEditInput, mangaId: Int) {
|
||||||
|
// Make sure change is defined
|
||||||
|
if (input.change == null) return
|
||||||
|
val (isRead, isBookmarked, lastPageRead) = input.change
|
||||||
|
if (isRead == null && isBookmarked == null) return
|
||||||
|
|
||||||
|
// Make sure some filter is defined
|
||||||
|
val condition = when {
|
||||||
|
input.chapterIds != null ->
|
||||||
|
Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.id inList input.chapterIds) }
|
||||||
|
input.chapterIndexes != null ->
|
||||||
|
Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder inList input.chapterIndexes) }
|
||||||
|
else -> null
|
||||||
|
} ?: return
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
val now = Instant.now().epochSecond
|
||||||
|
ChapterTable.update({ condition }) { update ->
|
||||||
|
isRead?.also {
|
||||||
|
update[ChapterTable.isRead] = it
|
||||||
|
}
|
||||||
|
isBookmarked?.also {
|
||||||
|
update[ChapterTable.isBookmarked] = it
|
||||||
|
}
|
||||||
|
lastPageRead?.also {
|
||||||
|
update[ChapterTable.lastPageRead] = it
|
||||||
|
update[ChapterTable.lastReadAt] = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getChaptersMetaMaps(chapterIds: List<EntityID<Int>>): Map<EntityID<Int>, Map<String, String>> {
|
fun getChaptersMetaMaps(chapterIds: List<EntityID<Int>>): Map<EntityID<Int>, Map<String, String>> {
|
||||||
return transaction {
|
return transaction {
|
||||||
ChapterMetaTable.select { ChapterMetaTable.ref inList chapterIds }
|
ChapterMetaTable.select { ChapterMetaTable.ref inList chapterIds }
|
||||||
|
|||||||
Reference in New Issue
Block a user