From cf73804c7162749cbf99045acd8570a8970e4875 Mon Sep 17 00:00:00 2001 From: Syer10 Date: Sat, 8 Apr 2023 16:42:55 -0400 Subject: [PATCH] Complete ExtensionQuery --- .../graphql/queries/ExtensionQuery.kt | 212 ++++++++++++++++-- .../tachidesk/graphql/queries/MetaQuery.kt | 3 +- .../graphql/server/primitives/OrderBy.kt | 22 +- .../tachidesk/graphql/types/ExtensionType.kt | 8 +- 4 files changed, 220 insertions(+), 25 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt index ff1b0f8a..4a87a983 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt @@ -9,24 +9,37 @@ package suwayomi.tachidesk.graphql.queries import com.expediagroup.graphql.server.extensions.getValueFromDataLoader import graphql.schema.DataFetchingEnvironment +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.Op +import org.jetbrains.exposed.sql.SortOrder +import org.jetbrains.exposed.sql.SqlExpressionBuilder.greater +import org.jetbrains.exposed.sql.SqlExpressionBuilder.less +import org.jetbrains.exposed.sql.andWhere import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter +import suwayomi.tachidesk.graphql.queries.filter.Filter +import suwayomi.tachidesk.graphql.queries.filter.HasGetOp +import suwayomi.tachidesk.graphql.queries.filter.IntFilter +import suwayomi.tachidesk.graphql.queries.filter.OpAnd +import suwayomi.tachidesk.graphql.queries.filter.StringFilter +import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompare +import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString +import suwayomi.tachidesk.graphql.queries.filter.applyOps +import suwayomi.tachidesk.graphql.server.primitives.Cursor +import suwayomi.tachidesk.graphql.server.primitives.OrderBy +import suwayomi.tachidesk.graphql.server.primitives.PageInfo +import suwayomi.tachidesk.graphql.server.primitives.QueryResults +import suwayomi.tachidesk.graphql.server.primitives.greaterNotUnique +import suwayomi.tachidesk.graphql.server.primitives.lessNotUnique +import suwayomi.tachidesk.graphql.server.primitives.maybeSwap import suwayomi.tachidesk.graphql.types.ExtensionNodeList -import suwayomi.tachidesk.graphql.types.ExtensionNodeList.Companion.toNodeList import suwayomi.tachidesk.graphql.types.ExtensionType import suwayomi.tachidesk.manga.model.table.ExtensionTable import java.util.concurrent.CompletableFuture /** * TODO Queries - * - Installed - * - HasUpdate - * - Obsolete - * - IsNsfw - * - In Pkg name list - * - Query name - * - Sort? - * - Paged Queries * * TODO Mutations * - Install @@ -39,11 +52,184 @@ class ExtensionQuery { return dataFetchingEnvironment.getValueFromDataLoader("ExtensionDataLoader", pkgName) } - fun extensions(): ExtensionNodeList { - val results = transaction { - ExtensionTable.selectAll().toList() + enum class ExtensionOrderBy(override val column: Column>) : OrderBy { + PKG_NAME(ExtensionTable.pkgName), + NAME(ExtensionTable.name), + APK_NAME(ExtensionTable.apkName); + + override fun greater(cursor: Cursor): Op { + return when (this) { + PKG_NAME -> ExtensionTable.pkgName greater cursor.value + NAME -> greaterNotUnique(ExtensionTable.name, ExtensionTable.pkgName, cursor, String::toString) + APK_NAME -> greaterNotUnique(ExtensionTable.apkName, ExtensionTable.pkgName, cursor, String::toString) + } } - return results.map { ExtensionType(it) }.toNodeList() + override fun less(cursor: Cursor): Op { + return when (this) { + PKG_NAME -> ExtensionTable.pkgName less cursor.value + NAME -> lessNotUnique(ExtensionTable.name, ExtensionTable.pkgName, cursor, String::toString) + APK_NAME -> lessNotUnique(ExtensionTable.apkName, ExtensionTable.pkgName, cursor, String::toString) + } + } + + override fun asCursor(type: ExtensionType): Cursor { + val value = when (this) { + PKG_NAME -> type.pkgName + NAME -> type.pkgName + "\\-" + type.name + APK_NAME -> type.pkgName + "\\-" + type.apkName + } + return Cursor(value) + } + } + + data class ExtensionCondition( + val apkName: String? = null, + val iconUrl: String? = null, + val name: String? = null, + val pkgName: String? = null, + val versionName: String? = null, + val versionCode: Int? = null, + val lang: String? = null, + val isNsfw: Boolean? = null, + val isInstalled: Boolean? = null, + val hasUpdate: Boolean? = null, + val isObsolete: Boolean? = null + ) : HasGetOp { + override fun getOp(): Op? { + val opAnd = OpAnd() + opAnd.eq(apkName, ExtensionTable.apkName) + opAnd.eq(iconUrl, ExtensionTable.iconUrl) + opAnd.eq(name, ExtensionTable.name) + opAnd.eq(versionName, ExtensionTable.versionName) + opAnd.eq(versionCode, ExtensionTable.versionCode) + opAnd.eq(lang, ExtensionTable.lang) + opAnd.eq(isNsfw, ExtensionTable.isNsfw) + opAnd.eq(isInstalled, ExtensionTable.isInstalled) + opAnd.eq(hasUpdate, ExtensionTable.hasUpdate) + opAnd.eq(isObsolete, ExtensionTable.isObsolete) + + return opAnd.op + } + } + + data class ExtensionFilter( + val apkName: StringFilter? = null, + val iconUrl: StringFilter? = null, + val name: StringFilter? = null, + val pkgName: StringFilter? = null, + val versionName: StringFilter? = null, + val versionCode: IntFilter? = null, + val lang: StringFilter? = null, + val isNsfw: BooleanFilter? = null, + val isInstalled: BooleanFilter? = null, + val hasUpdate: BooleanFilter? = null, + val isObsolete: BooleanFilter? = null, + override val and: List? = null, + override val or: List? = null, + override val not: ExtensionFilter? = null + ) : Filter { + override fun getOpList(): List> { + return listOfNotNull( + andFilterWithCompareString(ExtensionTable.apkName, apkName), + andFilterWithCompareString(ExtensionTable.iconUrl, iconUrl), + andFilterWithCompareString(ExtensionTable.name, name), + andFilterWithCompareString(ExtensionTable.pkgName, pkgName), + andFilterWithCompareString(ExtensionTable.versionName, versionName), + andFilterWithCompare(ExtensionTable.versionCode, versionCode), + andFilterWithCompareString(ExtensionTable.lang, lang), + andFilterWithCompare(ExtensionTable.isNsfw, isNsfw), + andFilterWithCompare(ExtensionTable.isInstalled, isInstalled), + andFilterWithCompare(ExtensionTable.hasUpdate, hasUpdate), + andFilterWithCompare(ExtensionTable.isObsolete, isObsolete) + ) + } + } + + fun extensions( + condition: ExtensionCondition? = null, + filter: ExtensionFilter? = null, + orderBy: ExtensionOrderBy? = null, + orderByType: SortOrder? = null, + before: Cursor? = null, + after: Cursor? = null, + first: Int? = null, + last: Int? = null, + offset: Int? = null + ): ExtensionNodeList { + val queryResults = transaction { + val res = ExtensionTable.selectAll() + + res.applyOps(condition, filter) + + if (orderBy != null || (last != null || before != null)) { + val orderByColumn = orderBy?.column ?: ExtensionTable.pkgName + val orderType = orderByType.maybeSwap(last ?: before) + + if (orderBy == ExtensionOrderBy.PKG_NAME || orderBy == null) { + res.orderBy(orderByColumn to orderType) + } else { + res.orderBy( + orderByColumn to orderType, + ExtensionTable.pkgName to SortOrder.ASC + ) + } + } + + val total = res.count() + val firstResult = res.firstOrNull()?.get(ExtensionTable.pkgName) + val lastResult = res.lastOrNull()?.get(ExtensionTable.pkgName) + + if (after != null) { + res.andWhere { + (orderBy ?: ExtensionOrderBy.PKG_NAME).greater(after) + } + } else if (before != null) { + res.andWhere { + (orderBy ?: ExtensionOrderBy.PKG_NAME).less(before) + } + } + + if (first != null) { + res.limit(first, offset?.toLong() ?: 0) + } else if (last != null) { + res.limit(last) + } + + QueryResults(total, firstResult, lastResult, res.toList()) + } + + val getAsCursor: (ExtensionType) -> Cursor = (orderBy ?: ExtensionOrderBy.PKG_NAME)::asCursor + + val resultsAsType = queryResults.results.map { ExtensionType(it) } + + return ExtensionNodeList( + resultsAsType, + if (resultsAsType.isEmpty()) { + emptyList() + } else { + listOfNotNull( + resultsAsType.firstOrNull()?.let { + ExtensionNodeList.ExtensionEdge( + getAsCursor(it), + it + ) + }, + resultsAsType.lastOrNull()?.let { + ExtensionNodeList.ExtensionEdge( + getAsCursor(it), + it + ) + } + ) + }, + pageInfo = PageInfo( + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.pkgName, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.pkgName, + startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, + endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) } + ), + totalCount = queryResults.total.toInt() + ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt index c760c9e0..f934ce2e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt @@ -38,7 +38,6 @@ import java.util.concurrent.CompletableFuture /** * TODO Queries - * - In list of keys * * TODO Mutations * - Add/update meta @@ -71,7 +70,7 @@ class MetaQuery { override fun asCursor(type: MetaItem): Cursor { val value = when (this) { KEY -> type.key - VALUE -> type.key + "-" + type.value + VALUE -> type.key + "\\-" + type.value } return Cursor(value) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt index a70ceecd..5b3e5f0c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt @@ -36,7 +36,12 @@ fun SortOrder?.maybeSwap(value: Any?): SortOrder { } } -fun > greaterNotUnique(column: Column, idColumn: Column>, cursor: Cursor, toValue: (String) -> T): Op { +fun > greaterNotUnique( + column: Column, + idColumn: Column>, + cursor: Cursor, + toValue: (String) -> T +): Op { val id = cursor.value.substringBefore('-').toInt() val value = toValue(cursor.value.substringAfter('-')) return (column greater value) or ((column eq value) and (idColumn greater id)) @@ -49,12 +54,17 @@ fun > greaterNotUnique( cursor: Cursor, toValue: (String) -> T ): Op { - val id = cursor.value.substringBefore('-') - val value = toValue(cursor.value.substringAfter('-')) + val id = cursor.value.substringBefore("\\-") + val value = toValue(cursor.value.substringAfter("\\-")) return (column greater value) or ((column eq value) and (idColumn greater id)) } -fun > lessNotUnique(column: Column, idColumn: Column>, cursor: Cursor, toValue: (String) -> T): Op { +fun > lessNotUnique( + column: Column, + idColumn: Column>, + cursor: Cursor, + toValue: (String) -> T +): Op { val id = cursor.value.substringBefore('-').toInt() val value = toValue(cursor.value.substringAfter('-')) return (column less value) or ((column eq value) and (idColumn less id)) @@ -67,7 +77,7 @@ fun > lessNotUnique( cursor: Cursor, toValue: (String) -> T ): Op { - val id = cursor.value.substringBefore('-') - val value = toValue(cursor.value.substringAfter('-')) + val id = cursor.value.substringBefore("\\-") + val value = toValue(cursor.value.substringAfter("\\-")) return (column less value) or ((column eq value) and (idColumn less id)) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt index 1c7c11a0..14d36f89 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt @@ -29,9 +29,9 @@ class ExtensionType( val lang: String, val isNsfw: Boolean, - val installed: Boolean, + val isInstalled: Boolean, val hasUpdate: Boolean, - val obsolete: Boolean + val isObsolete: Boolean ) : Node { constructor(row: ResultRow) : this( apkName = row[ExtensionTable.apkName], @@ -42,9 +42,9 @@ class ExtensionType( versionCode = row[ExtensionTable.versionCode], lang = row[ExtensionTable.lang], isNsfw = row[ExtensionTable.isNsfw], - installed = row[ExtensionTable.isInstalled], + isInstalled = row[ExtensionTable.isInstalled], hasUpdate = row[ExtensionTable.hasUpdate], - obsolete = row[ExtensionTable.isObsolete] + isObsolete = row[ExtensionTable.isObsolete] ) fun source(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture {