From 21719f4408d73a22cae2f197b078b3af1f08cbd8 Mon Sep 17 00:00:00 2001 From: Valter Martinek Date: Thu, 10 Nov 2022 22:42:12 +0100 Subject: [PATCH] Add basic graphql implementation with manga and chapters loading with data loaders --- server/build.gradle.kts | 4 ++ .../suwayomi/tachidesk/graphql/GraphQL.kt | 17 +++++ .../graphql/controller/GraphQLController.kt | 41 +++++++++++ .../graphql/dataLoaders/ChapterDataLoader.kt | 47 +++++++++++++ .../graphql/dataLoaders/MangaDataLoader.kt | 32 +++++++++ .../TachideskDataLoaderRegistryFactory.kt | 16 +++++ .../impl/JavalinGraphQLRequestParser.kt | 30 ++++++++ .../impl/TachideskGraphQLContextFactory.kt | 31 ++++++++ .../graphql/impl/TachideskGraphQLSchema.kt | 45 ++++++++++++ .../graphql/impl/TachideskGraphQLServer.kt | 29 ++++++++ .../tachidesk/graphql/queries/ChapterQuery.kt | 53 ++++++++++++++ .../tachidesk/graphql/queries/MangaQuery.kt | 56 +++++++++++++++ .../tachidesk/graphql/types/ChapterType.kt | 62 ++++++++++++++++ .../tachidesk/graphql/types/MangaType.kt | 70 +++++++++++++++++++ .../suwayomi/tachidesk/server/JavalinSetup.kt | 2 + 15 files changed, 535 insertions(+) create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/GraphQL.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ChapterDataLoader.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MangaDataLoader.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/TachideskDataLoaderRegistryFactory.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/JavalinGraphQLRequestParser.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLContextFactory.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLSchema.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLServer.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ChapterQuery.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ChapterType.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 57f3ffbb..cd386dee 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -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) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/GraphQL.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/GraphQL.kt new file mode 100644 index 00000000..6086ec00 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/GraphQL.kt @@ -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) + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt new file mode 100644 index 00000000..711e9cb5 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt @@ -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(HttpCode.OK) + } + ) +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ChapterDataLoader.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ChapterDataLoader.kt new file mode 100644 index 00000000..c3567893 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ChapterDataLoader.kt @@ -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 { + override val dataLoaderName = "ChapterDataLoader" + override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> + CompletableFuture.supplyAsync { + transaction { + addLogger(StdOutSqlLogger) + ChapterTable.select { ChapterTable.id inList ids } + .map { ChapterType(it) } + } + } + } +} + +class ChaptersForMangaDataLoader : KotlinDataLoader> { + override val dataLoaderName = "ChaptersForMangaDataLoader" + override fun getDataLoader(): DataLoader> = DataLoaderFactory.newDataLoader> { 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() } + } + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MangaDataLoader.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MangaDataLoader.kt new file mode 100644 index 00000000..d4804380 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MangaDataLoader.kt @@ -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 { + override val dataLoaderName = "MangaDataLoader" + override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> + CompletableFuture.supplyAsync { + transaction { + addLogger(StdOutSqlLogger) + MangaTable.select { MangaTable.id inList ids } + .map { MangaType(it) } + } + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/TachideskDataLoaderRegistryFactory.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/TachideskDataLoaderRegistryFactory.kt new file mode 100644 index 00000000..27ed965a --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/TachideskDataLoaderRegistryFactory.kt @@ -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() +) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/JavalinGraphQLRequestParser.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/JavalinGraphQLRequestParser.kt new file mode 100644 index 00000000..ad9b976f --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/JavalinGraphQLRequestParser.kt @@ -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 { + + @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.") + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLContextFactory.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLContextFactory.kt new file mode 100644 index 00000000..970c6c8f --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLContextFactory.kt @@ -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 { + override suspend fun generateContextMap(request: Context): Map<*, Any> = + mutableMapOf( +// "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 +// } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLSchema.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLSchema.kt new file mode 100644 index 00000000..dc9ed9c5 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLSchema.kt @@ -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() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLServer.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLServer.kt new file mode 100644 index 00000000..dbe2c6c7 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/impl/TachideskGraphQLServer.kt @@ -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(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) +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ChapterQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ChapterQuery.kt new file mode 100644 index 00000000..3dbaa076 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ChapterQuery.kt @@ -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 { + return dataFetchingEnvironment.getValueFromDataLoader("ChapterDataLoader", id) + } + + data class ChapterQueryInput( + val ids: List? = null, + val mangaIds: List? = null, + val page: Int? = null, + val count: Int? = null + ) + + fun chapters(input: ChapterQueryInput? = null): List { + 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) } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt new file mode 100644 index 00000000..9f19c65e --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt @@ -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 { + return dataFetchingEnvironment.getValueFromDataLoader("MangaDataLoader", id) + } + + data class MangaQueryInput( + val ids: List? = null, + val categoryIds: List? = null, + val page: Int? = null, + val count: Int? = null + ) + + fun mangas(input: MangaQueryInput? = null): List { + 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) } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ChapterType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ChapterType.kt new file mode 100644 index 00000000..f8536ad9 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ChapterType.kt @@ -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 = 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 { + return dataFetchingEnvironment.getValueFromDataLoader("MangaDataLoader", mangaId) + } + +// fun chapters(): List { +// return listOf("Foo", "Bar", "Baz") +// } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt new file mode 100644 index 00000000..50323872 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt @@ -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, + 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> { + return dataFetchingEnvironment.getValueFromDataLoader>("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!!) + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt index 7ccce731..cd7b7bb0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt @@ -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() } }