mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-03 19:04:39 -05: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(fileTree("lib/"))
|
||||||
implementation(kotlin("script-runtime"))
|
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)
|
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.conf.global
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import suwayomi.tachidesk.global.GlobalAPI
|
import suwayomi.tachidesk.global.GlobalAPI
|
||||||
|
import suwayomi.tachidesk.graphql.GraphQL
|
||||||
import suwayomi.tachidesk.manga.MangaAPI
|
import suwayomi.tachidesk.manga.MangaAPI
|
||||||
import suwayomi.tachidesk.server.util.Browser
|
import suwayomi.tachidesk.server.util.Browser
|
||||||
import suwayomi.tachidesk.server.util.setupWebInterface
|
import suwayomi.tachidesk.server.util.setupWebInterface
|
||||||
@@ -109,6 +110,7 @@ object JavalinSetup {
|
|||||||
GlobalAPI.defineEndpoints()
|
GlobalAPI.defineEndpoints()
|
||||||
MangaAPI.defineEndpoints()
|
MangaAPI.defineEndpoints()
|
||||||
}
|
}
|
||||||
|
GraphQL.defineEndpoints()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user