mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2026-02-03 18:07:59 +01:00
Add basic graphql implementation with manga and chapters loading with data loaders
This commit is contained in:
@@ -64,6 +64,10 @@ dependencies {
|
||||
// implementation(fileTree("lib/"))
|
||||
implementation(kotlin("script-runtime"))
|
||||
|
||||
implementation("com.expediagroup", "graphql-kotlin-server", "6.3.0")
|
||||
implementation("com.expediagroup", "graphql-kotlin-schema-generator", "6.3.0")
|
||||
implementation("com.graphql-java", "graphql-java-extended-scalars", "19.0")
|
||||
|
||||
testImplementation(libs.mockk)
|
||||
}
|
||||
|
||||
|
||||
17
server/src/main/kotlin/suwayomi/tachidesk/graphql/GraphQL.kt
Normal file
17
server/src/main/kotlin/suwayomi/tachidesk/graphql/GraphQL.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql
|
||||
|
||||
import io.javalin.apibuilder.ApiBuilder.post
|
||||
import suwayomi.tachidesk.graphql.controller.GraphQLController
|
||||
|
||||
object GraphQL {
|
||||
fun defineEndpoints() {
|
||||
post("graphql", GraphQLController.execute)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.controller
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import io.javalin.http.HttpCode
|
||||
import suwayomi.tachidesk.graphql.impl.getGraphQLServer
|
||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
import suwayomi.tachidesk.server.util.handler
|
||||
import suwayomi.tachidesk.server.util.withOperation
|
||||
|
||||
object GraphQLController {
|
||||
private val mapper = jacksonObjectMapper()
|
||||
private val tachideskGraphQLServer = getGraphQLServer(mapper)
|
||||
|
||||
/** execute graphql query */
|
||||
val execute = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("GraphQL endpoint")
|
||||
description("Endpoint for GraphQL endpoints")
|
||||
}
|
||||
},
|
||||
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future {
|
||||
tachideskGraphQLServer.execute(ctx)
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<Any>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.dataLoaders
|
||||
|
||||
import com.expediagroup.graphql.dataloader.KotlinDataLoader
|
||||
import org.dataloader.DataLoader
|
||||
import org.dataloader.DataLoaderFactory
|
||||
import org.jetbrains.exposed.sql.StdOutSqlLogger
|
||||
import org.jetbrains.exposed.sql.addLogger
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ChapterDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||
override val dataLoaderName = "ChapterDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType> = DataLoaderFactory.newDataLoader<Int, ChapterType> { ids ->
|
||||
CompletableFuture.supplyAsync {
|
||||
transaction {
|
||||
addLogger(StdOutSqlLogger)
|
||||
ChapterTable.select { ChapterTable.id inList ids }
|
||||
.map { ChapterType(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChaptersForMangaDataLoader : KotlinDataLoader<Int, List<ChapterType>> {
|
||||
override val dataLoaderName = "ChaptersForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, List<ChapterType>> = DataLoaderFactory.newDataLoader<Int, List<ChapterType>> { ids ->
|
||||
CompletableFuture.supplyAsync {
|
||||
transaction {
|
||||
addLogger(StdOutSqlLogger)
|
||||
val chaptersByMangaId = ChapterTable.select { ChapterTable.manga inList ids }
|
||||
.map { ChapterType(it) }
|
||||
.groupBy { it.mangaId }
|
||||
ids.map { chaptersByMangaId[it] ?: emptyList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.dataLoaders
|
||||
|
||||
import com.expediagroup.graphql.dataloader.KotlinDataLoader
|
||||
import org.dataloader.DataLoader
|
||||
import org.dataloader.DataLoaderFactory
|
||||
import org.jetbrains.exposed.sql.StdOutSqlLogger
|
||||
import org.jetbrains.exposed.sql.addLogger
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.types.MangaType
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class MangaDataLoader : KotlinDataLoader<Int, MangaType> {
|
||||
override val dataLoaderName = "MangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, MangaType> = DataLoaderFactory.newDataLoader<Int, MangaType> { ids ->
|
||||
CompletableFuture.supplyAsync {
|
||||
transaction {
|
||||
addLogger(StdOutSqlLogger)
|
||||
MangaTable.select { MangaTable.id inList ids }
|
||||
.map { MangaType(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.dataLoaders
|
||||
|
||||
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory
|
||||
|
||||
val tachideskDataLoaderRegistryFactory = KotlinDataLoaderRegistryFactory(
|
||||
MangaDataLoader(),
|
||||
ChapterDataLoader(),
|
||||
ChaptersForMangaDataLoader()
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.impl
|
||||
|
||||
import com.expediagroup.graphql.server.execution.GraphQLRequestParser
|
||||
import com.expediagroup.graphql.server.types.GraphQLServerRequest
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.javalin.http.Context
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Custom logic for how Javalin parses the incoming [Context] into the [GraphQLServerRequest]
|
||||
*/
|
||||
class JavalinGraphQLRequestParser(
|
||||
private val mapper: ObjectMapper
|
||||
) : GraphQLRequestParser<Context> {
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
override suspend fun parseRequest(context: Context): GraphQLServerRequest = try {
|
||||
val rawRequest = context.body()
|
||||
mapper.readValue(rawRequest, GraphQLServerRequest::class.java)
|
||||
} catch (e: IOException) {
|
||||
throw IOException("Unable to parse GraphQL payload.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.impl
|
||||
|
||||
import com.expediagroup.graphql.generator.execution.GraphQLContext
|
||||
import com.expediagroup.graphql.server.execution.GraphQLContextFactory
|
||||
import io.javalin.http.Context
|
||||
|
||||
/**
|
||||
* Custom logic for how Tachidesk should create its context given the [Context]
|
||||
*/
|
||||
class TachideskGraphQLContextFactory : GraphQLContextFactory<GraphQLContext, Context> {
|
||||
override suspend fun generateContextMap(request: Context): Map<*, Any> =
|
||||
mutableMapOf<Any, Any>(
|
||||
// "user" to User(
|
||||
// email = "fake@site.com",
|
||||
// firstName = "Someone",
|
||||
// lastName = "You Don't know",
|
||||
// universityId = 4
|
||||
// )
|
||||
).also { map ->
|
||||
// request.headers["my-custom-header"]?.let { customHeader ->
|
||||
// map["customHeader"] = customHeader
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.impl
|
||||
|
||||
import com.expediagroup.graphql.generator.SchemaGeneratorConfig
|
||||
import com.expediagroup.graphql.generator.TopLevelObject
|
||||
import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks
|
||||
import com.expediagroup.graphql.generator.scalars.IDValueUnboxer
|
||||
import com.expediagroup.graphql.generator.toSchema
|
||||
import graphql.GraphQL
|
||||
import graphql.scalars.ExtendedScalars
|
||||
import graphql.schema.GraphQLType
|
||||
import suwayomi.tachidesk.graphql.queries.ChapterQuery
|
||||
import suwayomi.tachidesk.graphql.queries.MangaQuery
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
class CustomSchemaGeneratorHooks : SchemaGeneratorHooks {
|
||||
override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) {
|
||||
Long::class -> ExtendedScalars.GraphQLLong
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val schema = toSchema(
|
||||
config = SchemaGeneratorConfig(
|
||||
supportedPackages = listOf("suwayomi.tachidesk.graphql"),
|
||||
introspectionEnabled = true,
|
||||
hooks = CustomSchemaGeneratorHooks()
|
||||
),
|
||||
queries = listOf(
|
||||
TopLevelObject(MangaQuery()),
|
||||
TopLevelObject(ChapterQuery())
|
||||
),
|
||||
mutations = listOf()
|
||||
)
|
||||
|
||||
fun getGraphQLObject(): GraphQL = GraphQL.newGraphQL(schema)
|
||||
.valueUnboxer(IDValueUnboxer())
|
||||
.build()
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.impl
|
||||
|
||||
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
||||
import com.expediagroup.graphql.server.execution.GraphQLServer
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.javalin.http.Context
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.tachideskDataLoaderRegistryFactory
|
||||
|
||||
class TachideskGraphQLServer(
|
||||
requestParser: JavalinGraphQLRequestParser,
|
||||
contextFactory: TachideskGraphQLContextFactory,
|
||||
requestHandler: GraphQLRequestHandler
|
||||
) : GraphQLServer<Context>(requestParser, contextFactory, requestHandler)
|
||||
|
||||
fun getGraphQLServer(mapper: ObjectMapper): TachideskGraphQLServer {
|
||||
val requestParser = JavalinGraphQLRequestParser(mapper)
|
||||
val contextFactory = TachideskGraphQLContextFactory()
|
||||
val graphQL = getGraphQLObject()
|
||||
val requestHandler = GraphQLRequestHandler(graphQL, tachideskDataLoaderRegistryFactory)
|
||||
|
||||
return TachideskGraphQLServer(requestParser, contextFactory, requestHandler)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.queries
|
||||
|
||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||
import graphql.schema.DataFetchingEnvironment
|
||||
import org.jetbrains.exposed.sql.andWhere
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ChapterQuery {
|
||||
fun chapter(dataFetchingEnvironment: DataFetchingEnvironment, id: Int): CompletableFuture<ChapterType> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader<Int, ChapterType>("ChapterDataLoader", id)
|
||||
}
|
||||
|
||||
data class ChapterQueryInput(
|
||||
val ids: List<Int>? = null,
|
||||
val mangaIds: List<Int>? = null,
|
||||
val page: Int? = null,
|
||||
val count: Int? = null
|
||||
)
|
||||
|
||||
fun chapters(input: ChapterQueryInput? = null): List<ChapterType> {
|
||||
val results = transaction {
|
||||
var res = ChapterTable.selectAll()
|
||||
|
||||
if (input != null) {
|
||||
if (input.mangaIds != null) {
|
||||
res = res.andWhere { ChapterTable.manga inList input.mangaIds }
|
||||
}
|
||||
if (input.ids != null) {
|
||||
res = res.andWhere { ChapterTable.id inList input.ids }
|
||||
}
|
||||
if (input.count != null) {
|
||||
val offset = if (input.page == null) 0 else (input.page * input.count).toLong()
|
||||
res = res.limit(input.count, offset)
|
||||
}
|
||||
}
|
||||
|
||||
res.toList()
|
||||
}
|
||||
|
||||
return results.map { ChapterType(it) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.queries
|
||||
|
||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||
import graphql.schema.DataFetchingEnvironment
|
||||
import org.jetbrains.exposed.sql.andWhere
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.types.MangaType
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class MangaQuery {
|
||||
fun manga(dataFetchingEnvironment: DataFetchingEnvironment, id: Int): CompletableFuture<MangaType> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader<Int, MangaType>("MangaDataLoader", id)
|
||||
}
|
||||
|
||||
data class MangaQueryInput(
|
||||
val ids: List<Int>? = null,
|
||||
val categoryIds: List<Int>? = null,
|
||||
val page: Int? = null,
|
||||
val count: Int? = null
|
||||
)
|
||||
|
||||
fun mangas(input: MangaQueryInput? = null): List<MangaType> {
|
||||
val results = transaction {
|
||||
var res = MangaTable.selectAll()
|
||||
|
||||
if (input != null) {
|
||||
if (input.categoryIds != null) {
|
||||
res = MangaTable.innerJoin(CategoryMangaTable)
|
||||
.select { CategoryMangaTable.category inList input.categoryIds }
|
||||
}
|
||||
if (input.ids != null) {
|
||||
res.andWhere { MangaTable.id inList input.ids }
|
||||
}
|
||||
if (input.count != null) {
|
||||
val offset = if (input.page == null) 0 else (input.page * input.count).toLong()
|
||||
res.limit(input.count, offset)
|
||||
}
|
||||
}
|
||||
|
||||
res.toList()
|
||||
}
|
||||
|
||||
return results.map { MangaType(it) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.types
|
||||
|
||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||
import graphql.schema.DataFetchingEnvironment
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ChapterType(
|
||||
val id: Int,
|
||||
val url: String,
|
||||
val name: String,
|
||||
val uploadDate: Long,
|
||||
val chapterNumber: Float,
|
||||
val scanlator: String?,
|
||||
val mangaId: Int,
|
||||
val isRead: Boolean,
|
||||
val isBookmarked: Boolean,
|
||||
val lastPageRead: Int,
|
||||
val lastReadAt: Long,
|
||||
val sourceOrder: Int,
|
||||
val fetchedAt: Long,
|
||||
val isDownloaded: Boolean,
|
||||
val pageCount: Int
|
||||
// val chapterCount: Int?,
|
||||
// val meta: Map<String, String> = emptyMap()
|
||||
) {
|
||||
constructor(row: ResultRow) : this(
|
||||
row[ChapterTable.id].value,
|
||||
row[ChapterTable.url],
|
||||
row[ChapterTable.name],
|
||||
row[ChapterTable.date_upload],
|
||||
row[ChapterTable.chapter_number],
|
||||
row[ChapterTable.scanlator],
|
||||
row[ChapterTable.manga].value,
|
||||
row[ChapterTable.isRead],
|
||||
row[ChapterTable.isBookmarked],
|
||||
row[ChapterTable.lastPageRead],
|
||||
row[ChapterTable.lastReadAt],
|
||||
row[ChapterTable.sourceOrder],
|
||||
row[ChapterTable.fetchedAt],
|
||||
row[ChapterTable.isDownloaded],
|
||||
row[ChapterTable.pageCount]
|
||||
// transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() },
|
||||
// Chapter.getChapterMetaMap(chapterEntry[id])
|
||||
)
|
||||
|
||||
fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaType> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader<Int, MangaType>("MangaDataLoader", mangaId)
|
||||
}
|
||||
|
||||
// fun chapters(): List<String> {
|
||||
// return listOf("Foo", "Bar", "Baz")
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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/. */
|
||||
|
||||
package suwayomi.tachidesk.graphql.types
|
||||
|
||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||
import graphql.schema.DataFetchingEnvironment
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
||||
import suwayomi.tachidesk.manga.model.table.MangaStatus
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class MangaType(
|
||||
val id: Int,
|
||||
val sourceId: String,
|
||||
val url: String,
|
||||
val title: String,
|
||||
val thumbnailUrl: String?,
|
||||
val initialized: Boolean,
|
||||
val artist: String?,
|
||||
val author: String?,
|
||||
val description: String?,
|
||||
val genre: List<String>,
|
||||
val status: String,
|
||||
val inLibrary: Boolean,
|
||||
val inLibraryAt: Long,
|
||||
val realUrl: String?,
|
||||
var lastFetchedAt: Long?,
|
||||
var chaptersLastFetchedAt: Long?
|
||||
) {
|
||||
constructor(row: ResultRow) : this(
|
||||
row[MangaTable.id].value,
|
||||
row[MangaTable.sourceReference].toString(),
|
||||
row[MangaTable.url],
|
||||
row[MangaTable.title],
|
||||
row[MangaTable.thumbnail_url],
|
||||
row[MangaTable.initialized],
|
||||
row[MangaTable.artist],
|
||||
row[MangaTable.author],
|
||||
row[MangaTable.description],
|
||||
row[MangaTable.genre].toGenreList(),
|
||||
MangaStatus.valueOf(row[MangaTable.status]).name,
|
||||
row[MangaTable.inLibrary],
|
||||
row[MangaTable.inLibraryAt],
|
||||
row[MangaTable.realUrl],
|
||||
row[MangaTable.lastFetchedAt],
|
||||
row[MangaTable.chaptersLastFetchedAt]
|
||||
)
|
||||
|
||||
fun chapters(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<List<ChapterType>> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader<Int, List<ChapterType>>("ChaptersForMangaDataLoader", id)
|
||||
}
|
||||
|
||||
fun age(): Long? {
|
||||
if (lastFetchedAt == null) return null
|
||||
return Instant.now().epochSecond.minus(lastFetchedAt!!)
|
||||
}
|
||||
|
||||
fun chaptersAge(): Long? {
|
||||
if (chaptersLastFetchedAt == null) return null
|
||||
|
||||
return Instant.now().epochSecond.minus(chaptersLastFetchedAt!!)
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.global.GlobalAPI
|
||||
import suwayomi.tachidesk.graphql.GraphQL
|
||||
import suwayomi.tachidesk.manga.MangaAPI
|
||||
import suwayomi.tachidesk.server.util.Browser
|
||||
import suwayomi.tachidesk.server.util.setupWebInterface
|
||||
@@ -109,6 +110,7 @@ object JavalinSetup {
|
||||
GlobalAPI.defineEndpoints()
|
||||
MangaAPI.defineEndpoints()
|
||||
}
|
||||
GraphQL.defineEndpoints()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user