mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-03 02:44:34 -05:00
changes needed for tachiyomi tracker
This commit is contained in:
@@ -12,6 +12,7 @@ import io.javalin.apibuilder.ApiBuilder.get
|
|||||||
import io.javalin.apibuilder.ApiBuilder.patch
|
import io.javalin.apibuilder.ApiBuilder.patch
|
||||||
import io.javalin.apibuilder.ApiBuilder.path
|
import io.javalin.apibuilder.ApiBuilder.path
|
||||||
import io.javalin.apibuilder.ApiBuilder.post
|
import io.javalin.apibuilder.ApiBuilder.post
|
||||||
|
import io.javalin.apibuilder.ApiBuilder.put
|
||||||
import io.javalin.apibuilder.ApiBuilder.ws
|
import io.javalin.apibuilder.ApiBuilder.ws
|
||||||
import suwayomi.tachidesk.manga.controller.BackupController
|
import suwayomi.tachidesk.manga.controller.BackupController
|
||||||
import suwayomi.tachidesk.manga.controller.CategoryController
|
import suwayomi.tachidesk.manga.controller.CategoryController
|
||||||
@@ -54,6 +55,7 @@ object MangaAPI {
|
|||||||
|
|
||||||
path("manga") {
|
path("manga") {
|
||||||
get("{mangaId}", MangaController.retrieve)
|
get("{mangaId}", MangaController.retrieve)
|
||||||
|
get("{mangaId}/full", MangaController.retrieveFull)
|
||||||
get("{mangaId}/thumbnail", MangaController.thumbnail)
|
get("{mangaId}/thumbnail", MangaController.thumbnail)
|
||||||
|
|
||||||
get("{mangaId}/category", MangaController.categoryList)
|
get("{mangaId}/category", MangaController.categoryList)
|
||||||
@@ -69,6 +71,7 @@ object MangaAPI {
|
|||||||
post("{mangaId}/chapter/batch", MangaController.chapterBatch)
|
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)
|
||||||
|
put("{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)
|
||||||
|
|||||||
@@ -33,13 +33,12 @@ import kotlin.time.Duration.Companion.days
|
|||||||
object MangaController {
|
object MangaController {
|
||||||
private val json by DI.global.instance<Json>()
|
private val json by DI.global.instance<Json>()
|
||||||
|
|
||||||
/** get manga info */
|
|
||||||
val retrieve = handler(
|
val retrieve = handler(
|
||||||
pathParam<Int>("mangaId"),
|
pathParam<Int>("mangaId"),
|
||||||
queryParam("onlineFetch", false),
|
queryParam("onlineFetch", false),
|
||||||
documentWith = {
|
documentWith = {
|
||||||
withOperation {
|
withOperation {
|
||||||
summary("Get a manga")
|
summary("Get manga info")
|
||||||
description("Get a manga from the database using a specific id.")
|
description("Get a manga from the database using a specific id.")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -56,6 +55,29 @@ object MangaController {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** get manga info with all data filled in */
|
||||||
|
val retrieveFull = handler(
|
||||||
|
pathParam<Int>("mangaId"),
|
||||||
|
queryParam("onlineFetch", false),
|
||||||
|
documentWith = {
|
||||||
|
withOperation {
|
||||||
|
summary("Get manga info with all data filled in")
|
||||||
|
description("Get a manga from the database using a specific id.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
behaviorOf = { ctx, mangaId, onlineFetch ->
|
||||||
|
ctx.future(
|
||||||
|
future {
|
||||||
|
Manga.getMangaFull(mangaId, onlineFetch)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
withResults = {
|
||||||
|
json<MangaDataClass>(HttpCode.OK)
|
||||||
|
httpCode(HttpCode.NOT_FOUND)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/** manga thumbnail */
|
/** manga thumbnail */
|
||||||
val thumbnail = handler(
|
val thumbnail = handler(
|
||||||
pathParam<Int>("mangaId"),
|
pathParam<Int>("mangaId"),
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ object CategoryManga {
|
|||||||
|
|
||||||
val transform: (ResultRow) -> MangaDataClass = {
|
val transform: (ResultRow) -> MangaDataClass = {
|
||||||
val dataClass = MangaTable.toDataClass(it)
|
val dataClass = MangaTable.toDataClass(it)
|
||||||
dataClass.unreadCount = it[unreadExpression]?.toInt()
|
dataClass.unreadCount = it[unreadExpression]
|
||||||
dataClass.downloadCount = it[downloadExpression]?.toInt()
|
dataClass.downloadCount = it[downloadExpression]
|
||||||
dataClass.chapterCount = it[chapterCountExpression]?.toInt()
|
dataClass.chapterCount = it[chapterCountExpression]
|
||||||
dataClass
|
dataClass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.source.local.LocalSource
|
|||||||
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 org.jetbrains.exposed.sql.ResultRow
|
import org.jetbrains.exposed.sql.ResultRow
|
||||||
|
import org.jetbrains.exposed.sql.SortOrder
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
@@ -34,9 +35,11 @@ import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
|||||||
import suwayomi.tachidesk.manga.impl.util.updateMangaDownloadDir
|
import suwayomi.tachidesk.manga.impl.util.updateMangaDownloadDir
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||||
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
||||||
|
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaStatus
|
import suwayomi.tachidesk.manga.model.table.MangaStatus
|
||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
|
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||||
import suwayomi.tachidesk.server.ApplicationDirs
|
import suwayomi.tachidesk.server.ApplicationDirs
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -127,6 +130,40 @@ object Manga {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getMangaFull(mangaId: Int, onlineFetch: Boolean = false): MangaDataClass {
|
||||||
|
val mangaDaaClass = getManga(mangaId, onlineFetch)
|
||||||
|
|
||||||
|
return transaction {
|
||||||
|
val unreadCount =
|
||||||
|
ChapterTable
|
||||||
|
.select { (ChapterTable.manga eq mangaId) and (ChapterTable.isRead eq false) }
|
||||||
|
.count()
|
||||||
|
|
||||||
|
val downloadCount =
|
||||||
|
ChapterTable
|
||||||
|
.select { (ChapterTable.manga eq mangaId) and (ChapterTable.isDownloaded eq true) }
|
||||||
|
.count()
|
||||||
|
|
||||||
|
val chapterCount =
|
||||||
|
ChapterTable
|
||||||
|
.select { (ChapterTable.manga eq mangaId) }
|
||||||
|
.count()
|
||||||
|
|
||||||
|
val lastChapterRead =
|
||||||
|
ChapterTable
|
||||||
|
.select { (ChapterTable.manga eq mangaId) }
|
||||||
|
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
||||||
|
.firstOrNull { it[ChapterTable.isRead] }
|
||||||
|
|
||||||
|
mangaDaaClass.unreadCount = unreadCount
|
||||||
|
mangaDaaClass.downloadCount = downloadCount
|
||||||
|
mangaDaaClass.chapterCount = chapterCount
|
||||||
|
mangaDaaClass.lastChapterRead = lastChapterRead?.let { ChapterTable.toDataClass(it) }
|
||||||
|
|
||||||
|
mangaDaaClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getMangaDataClass(mangaId: Int, mangaEntry: ResultRow) = MangaDataClass(
|
private fun getMangaDataClass(mangaId: Int, mangaEntry: ResultRow) = MangaDataClass(
|
||||||
mangaId,
|
mangaId,
|
||||||
mangaEntry[MangaTable.sourceReference].toString(),
|
mangaEntry[MangaTable.sourceReference].toString(),
|
||||||
@@ -206,6 +243,7 @@ object Manga {
|
|||||||
GET(thumbnailUrl, source.headers)
|
GET(thumbnailUrl, source.headers)
|
||||||
).await()
|
).await()
|
||||||
}
|
}
|
||||||
|
|
||||||
is LocalSource -> {
|
is LocalSource -> {
|
||||||
val imageFile = mangaEntry[MangaTable.thumbnail_url]?.let {
|
val imageFile = mangaEntry[MangaTable.thumbnail_url]?.let {
|
||||||
val file = File(it)
|
val file = File(it)
|
||||||
@@ -219,6 +257,7 @@ object Manga {
|
|||||||
?: "image/jpeg"
|
?: "image/jpeg"
|
||||||
imageFile.inputStream() to contentType
|
imageFile.inputStream() to contentType
|
||||||
}
|
}
|
||||||
|
|
||||||
is StubSource -> getImageResponse(saveDir, fileName, useCache) {
|
is StubSource -> getImageResponse(saveDir, fileName, useCache) {
|
||||||
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||||
?: throw NullPointerException("No thumbnail found")
|
?: throw NullPointerException("No thumbnail found")
|
||||||
@@ -226,6 +265,7 @@ object Manga {
|
|||||||
GET(thumbnailUrl)
|
GET(thumbnailUrl)
|
||||||
).await()
|
).await()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("Unknown source")
|
else -> throw IllegalArgumentException("Unknown source")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,10 @@ data class MangaDataClass(
|
|||||||
var chaptersLastFetchedAt: Long? = 0,
|
var chaptersLastFetchedAt: Long? = 0,
|
||||||
|
|
||||||
val freshData: Boolean = false,
|
val freshData: Boolean = false,
|
||||||
var unreadCount: Int? = null,
|
var unreadCount: Long? = null,
|
||||||
var downloadCount: Int? = null,
|
var downloadCount: Long? = null,
|
||||||
var chapterCount: Int? = null,
|
var chapterCount: Long? = null,
|
||||||
|
var lastChapterRead: ChapterDataClass? = null,
|
||||||
|
|
||||||
val age: Long? = if (lastFetchedAt == null) 0 else Instant.now().epochSecond.minus(lastFetchedAt),
|
val age: Long? = if (lastFetchedAt == null) 0 else Instant.now().epochSecond.minus(lastFetchedAt),
|
||||||
val chaptersAge: Long? = if (chaptersLastFetchedAt == null) null else Instant.now().epochSecond.minus(chaptersLastFetchedAt)
|
val chaptersAge: Long? = if (chaptersLastFetchedAt == null) null else Instant.now().epochSecond.minus(chaptersLastFetchedAt)
|
||||||
|
|||||||
Reference in New Issue
Block a user