mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-05 03:44:36 -05:00
Implement extension store
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package suwayomi.tachidesk.graphql.dataLoaders
|
||||
|
||||
import com.expediagroup.graphql.dataloader.KotlinDataLoader
|
||||
import graphql.GraphQLContext
|
||||
import org.dataloader.DataLoader
|
||||
import org.dataloader.DataLoaderFactory
|
||||
import org.jetbrains.exposed.v1.core.Slf4jSqlDebugLogger
|
||||
import org.jetbrains.exposed.v1.core.inList
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.types.ExtensionNodeList
|
||||
import suwayomi.tachidesk.graphql.types.ExtensionNodeList.Companion.toNodeList
|
||||
import suwayomi.tachidesk.graphql.types.ExtensionStoreType
|
||||
import suwayomi.tachidesk.graphql.types.ExtensionType
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionStoreTable
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class ExtensionStoreForExtension : KotlinDataLoader<String, ExtensionStoreType> {
|
||||
override val dataLoaderName = "ExtensionStoreForExtension"
|
||||
|
||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionStoreType> =
|
||||
DataLoaderFactory.newDataLoader<String, ExtensionStoreType> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val extensionStoreByIndexUrl =
|
||||
ExtensionStoreTable
|
||||
.selectAll()
|
||||
.where { ExtensionStoreTable.indexUrl inList ids }
|
||||
.map { ExtensionStoreType(it) }
|
||||
.associateBy { it.indexUrl }
|
||||
ids.map { (extensionStoreByIndexUrl[it]) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionForExtensionStore : KotlinDataLoader<String, ExtensionNodeList> {
|
||||
override val dataLoaderName = "ExtensionForExtensionStore"
|
||||
|
||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionNodeList> =
|
||||
DataLoaderFactory.newDataLoader<String, ExtensionNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val extensionByIndexUrl =
|
||||
ExtensionTable
|
||||
.selectAll()
|
||||
.where { ExtensionTable.storeIndexUrl inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
.groupBy { it.storeIndexUrl }
|
||||
ids.map { (extensionByIndexUrl[it] ?: emptyList()).toNodeList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
/*
|
||||
* 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/. */
|
||||
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||
import suwayomi.tachidesk.graphql.types.ExtensionStoreType
|
||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionStoreService
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionStoreTable
|
||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
import suwayomi.tachidesk.server.serverConfig
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ExtensionStoreMutation {
|
||||
data class AddExtensionStoreInput(
|
||||
val clientMutationId: String? = null,
|
||||
val indexUrl: String,
|
||||
)
|
||||
|
||||
data class AddExtensionStorePayload(
|
||||
val clientMutationId: String?,
|
||||
val extensionStore: ExtensionStoreType,
|
||||
)
|
||||
|
||||
@RequireAuth
|
||||
fun addExtensionStore(input: AddExtensionStoreInput): CompletableFuture<AddExtensionStorePayload?> {
|
||||
val (clientMutationId, indexUrl) = input
|
||||
return future {
|
||||
val store = ExtensionStoreService.fetch(indexUrl)
|
||||
|
||||
ExtensionStoreService.upsert(store)
|
||||
serverConfig.extensionStores.value = (serverConfig.extensionStores.value + indexUrl).distinct()
|
||||
val row =
|
||||
transaction {
|
||||
ExtensionStoreTable
|
||||
.selectAll()
|
||||
.where { ExtensionStoreTable.indexUrl eq store.indexUrl }
|
||||
.first()
|
||||
}
|
||||
|
||||
AddExtensionStorePayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensionStore = ExtensionStoreType(row),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class RemoveExtensionStoreInput(
|
||||
val clientMutationId: String? = null,
|
||||
val indexUrl: String,
|
||||
)
|
||||
|
||||
data class RemoveExtensionStorePayload(
|
||||
val clientMutationId: String?,
|
||||
val extensionStore: ExtensionStoreType?,
|
||||
)
|
||||
|
||||
@RequireAuth
|
||||
fun removeExtensionStore(input: RemoveExtensionStoreInput): CompletableFuture<RemoveExtensionStorePayload?> {
|
||||
val (clientMutationId, indexUrl) = input
|
||||
return future {
|
||||
val store =
|
||||
transaction {
|
||||
ExtensionStoreTable
|
||||
.selectAll()
|
||||
.where { ExtensionStoreTable.indexUrl eq indexUrl }
|
||||
.firstOrNull()
|
||||
?.let { ExtensionStoreType(it) }
|
||||
}
|
||||
|
||||
store?.let {
|
||||
transaction {
|
||||
ExtensionStoreTable.deleteWhere { ExtensionStoreTable.indexUrl eq indexUrl }
|
||||
}
|
||||
}
|
||||
|
||||
serverConfig.extensionStores.value = serverConfig.extensionStores.value.filterNot { it == indexUrl }
|
||||
|
||||
RemoveExtensionStorePayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensionStore =
|
||||
store?.let {
|
||||
ExtensionStoreType(
|
||||
name = it.name,
|
||||
badgeLabel = it.badgeLabel,
|
||||
signingKey = it.signingKey,
|
||||
contactWebsite = it.contactWebsite,
|
||||
contactDiscord = it.contactDiscord,
|
||||
indexUrl = it.indexUrl,
|
||||
isLegacy = it.isLegacy,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,12 +190,13 @@ class MangaMutation {
|
||||
var mangaEntry =
|
||||
transaction { MangaTable.selectAll().where { MangaTable.id eq id }.first() }
|
||||
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
val sMangaUpdate = Manga.fetchMangaAndChapters(
|
||||
mangaEntry = mangaEntry,
|
||||
source = source,
|
||||
fetchDetails = fetchManga,
|
||||
fetchChapters = fetchChapters
|
||||
)
|
||||
val sMangaUpdate =
|
||||
Manga.fetchMangaAndChapters(
|
||||
mangaEntry = mangaEntry,
|
||||
source = source,
|
||||
fetchDetails = fetchManga,
|
||||
fetchChapters = fetchChapters,
|
||||
)
|
||||
|
||||
Manga.updateMangaDatabase(mangaEntry, source, sMangaUpdate.manga)
|
||||
mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq id }.first() }
|
||||
@@ -209,9 +210,8 @@ class MangaMutation {
|
||||
.selectAll()
|
||||
.where { ChapterTable.manga eq id }
|
||||
.orderBy(ChapterTable.sourceOrder)
|
||||
.map { ChapterType(it) }
|
||||
.map { ChapterType(it) },
|
||||
)
|
||||
|
||||
}
|
||||
FetchMangaAndChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
|
||||
@@ -24,6 +24,7 @@ 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.LongFilter
|
||||
import suwayomi.tachidesk.graphql.queries.filter.OpAnd
|
||||
import suwayomi.tachidesk.graphql.queries.filter.StringFilter
|
||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompare
|
||||
@@ -38,6 +39,7 @@ import suwayomi.tachidesk.graphql.server.primitives.applyBeforeAfter
|
||||
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.ContentRating
|
||||
import suwayomi.tachidesk.graphql.types.ExtensionNodeList
|
||||
import suwayomi.tachidesk.graphql.types.ExtensionType
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
@@ -55,21 +57,23 @@ class ExtensionQuery {
|
||||
) : OrderBy<ExtensionType> {
|
||||
PKG_NAME(ExtensionTable.pkgName),
|
||||
NAME(ExtensionTable.name),
|
||||
APK_NAME(ExtensionTable.apkName),
|
||||
|
||||
@GraphQLDeprecated("")
|
||||
APK_NAME(ExtensionTable.pkgName),
|
||||
;
|
||||
|
||||
override fun greater(cursor: Cursor): Op<Boolean> =
|
||||
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)
|
||||
APK_NAME -> ExtensionTable.pkgName greater cursor.value
|
||||
}
|
||||
|
||||
override fun less(cursor: Cursor): Op<Boolean> =
|
||||
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)
|
||||
APK_NAME -> ExtensionTable.pkgName less cursor.value
|
||||
}
|
||||
|
||||
override fun asCursor(type: ExtensionType): Cursor {
|
||||
@@ -89,29 +93,41 @@ class ExtensionQuery {
|
||||
) : Order<ExtensionOrderBy>
|
||||
|
||||
data class ExtensionCondition(
|
||||
val storeIndexUrl: String? = null,
|
||||
@GraphQLDeprecated("", ReplaceWith("storeIndexUrl"))
|
||||
val repo: String? = null,
|
||||
val apkName: String? = null,
|
||||
val iconUrl: String? = null,
|
||||
val name: String? = null,
|
||||
val pkgName: String? = null,
|
||||
val apkUrl: String? = null,
|
||||
val extensionLib: String? = null,
|
||||
val versionName: String? = null,
|
||||
val versionCode: Int? = null,
|
||||
val versionCodeLong: Long? = null,
|
||||
val lang: String? = null,
|
||||
@GraphQLDeprecated("", ReplaceWith("contentRating"))
|
||||
val isNsfw: Boolean? = null,
|
||||
val contentRating: ContentRating? = null,
|
||||
val isInstalled: Boolean? = null,
|
||||
val hasUpdate: Boolean? = null,
|
||||
val isObsolete: Boolean? = null,
|
||||
) : HasGetOp {
|
||||
override fun getOp(): Op<Boolean>? {
|
||||
val opAnd = OpAnd()
|
||||
opAnd.eq(repo, ExtensionTable.repo)
|
||||
opAnd.eq(storeIndexUrl, ExtensionTable.storeIndexUrl)
|
||||
opAnd.eq(repo, ExtensionTable.storeIndexUrl)
|
||||
opAnd.eq(apkName, ExtensionTable.apkName)
|
||||
opAnd.eq(iconUrl, ExtensionTable.iconUrl)
|
||||
opAnd.eq(apkUrl, ExtensionTable.apkUrl)
|
||||
opAnd.eq(name, ExtensionTable.name)
|
||||
opAnd.eq(extensionLib, ExtensionTable.extensionLib)
|
||||
opAnd.eq(versionName, ExtensionTable.versionName)
|
||||
opAnd.eq(versionCode, ExtensionTable.versionCode)
|
||||
opAnd.eq(versionCode?.toLong(), ExtensionTable.versionCode)
|
||||
opAnd.eq(versionCodeLong, ExtensionTable.versionCode)
|
||||
opAnd.eq(lang, ExtensionTable.lang)
|
||||
opAnd.eq(isNsfw, ExtensionTable.isNsfw)
|
||||
opAnd.eq(isNsfw?.let { if (it) 3 else 0 }, ExtensionTable.contentRating)
|
||||
opAnd.eq(contentRating?.ordinal, ExtensionTable.contentRating)
|
||||
opAnd.eq(isInstalled, ExtensionTable.isInstalled)
|
||||
opAnd.eq(hasUpdate, ExtensionTable.hasUpdate)
|
||||
opAnd.eq(isObsolete, ExtensionTable.isObsolete)
|
||||
@@ -121,15 +137,23 @@ class ExtensionQuery {
|
||||
}
|
||||
|
||||
data class ExtensionFilter(
|
||||
val storeIndexUrl: StringFilter? = null,
|
||||
@GraphQLDeprecated("", ReplaceWith("storeIndexUrl"))
|
||||
val repo: StringFilter? = null,
|
||||
val apkName: StringFilter? = null,
|
||||
val iconUrl: StringFilter? = null,
|
||||
val name: StringFilter? = null,
|
||||
val pkgName: StringFilter? = null,
|
||||
val apkUrl: StringFilter? = null,
|
||||
val versionName: StringFilter? = null,
|
||||
val extensionLib: StringFilter? = null,
|
||||
@GraphQLDeprecated("", ReplaceWith("versionCodeLong"))
|
||||
val versionCode: IntFilter? = null,
|
||||
val versionCodeLong: LongFilter? = null,
|
||||
val lang: StringFilter? = null,
|
||||
@GraphQLDeprecated("", ReplaceWith("storeIndexUrl"))
|
||||
val isNsfw: BooleanFilter? = null,
|
||||
// val contentRating: EnumFilter<ContentRating>? = null,
|
||||
val isInstalled: BooleanFilter? = null,
|
||||
val hasUpdate: BooleanFilter? = null,
|
||||
val isObsolete: BooleanFilter? = null,
|
||||
@@ -139,15 +163,18 @@ class ExtensionQuery {
|
||||
) : Filter<ExtensionFilter> {
|
||||
override fun getOpList(): List<Op<Boolean>> =
|
||||
listOfNotNull(
|
||||
andFilterWithCompareString(ExtensionTable.repo, repo),
|
||||
andFilterWithCompareString(ExtensionTable.storeIndexUrl, storeIndexUrl),
|
||||
andFilterWithCompareString(ExtensionTable.storeIndexUrl, repo),
|
||||
andFilterWithCompareString(ExtensionTable.apkName, apkName),
|
||||
andFilterWithCompareString(ExtensionTable.iconUrl, iconUrl),
|
||||
andFilterWithCompareString(ExtensionTable.name, name),
|
||||
andFilterWithCompareString(ExtensionTable.pkgName, pkgName),
|
||||
andFilterWithCompareString(ExtensionTable.apkUrl, apkUrl),
|
||||
andFilterWithCompareString(ExtensionTable.extensionLib, extensionLib),
|
||||
andFilterWithCompareString(ExtensionTable.versionName, versionName),
|
||||
andFilterWithCompare(ExtensionTable.versionCode, versionCode),
|
||||
andFilterWithCompare(ExtensionTable.versionCode, versionCodeLong),
|
||||
andFilterWithCompareString(ExtensionTable.lang, lang),
|
||||
andFilterWithCompare(ExtensionTable.isNsfw, isNsfw),
|
||||
// andFilterWithCompareEnum(ExtensionTable.contentRating, contentRating),
|
||||
andFilterWithCompare(ExtensionTable.isInstalled, isInstalled),
|
||||
andFilterWithCompare(ExtensionTable.hasUpdate, hasUpdate),
|
||||
andFilterWithCompare(ExtensionTable.isObsolete, isObsolete),
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package suwayomi.tachidesk.graphql.queries
|
||||
|
||||
/*
|
||||
* 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/. */
|
||||
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||
import suwayomi.tachidesk.graphql.types.ExtensionStoreType
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionStoreTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ExtensionStoreQuery {
|
||||
@RequireAuth
|
||||
fun extensionStore(indexUrl: String): CompletableFuture<ExtensionStoreType?> =
|
||||
CompletableFuture.supplyAsync {
|
||||
transaction {
|
||||
ExtensionStoreTable
|
||||
.selectAll()
|
||||
.where { ExtensionStoreTable.indexUrl eq indexUrl }
|
||||
.firstOrNull()
|
||||
?.let { ExtensionStoreType(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@RequireAuth
|
||||
fun extensionStores(): List<ExtensionStoreType> =
|
||||
transaction {
|
||||
ExtensionStoreTable
|
||||
.selectAll()
|
||||
.toList()
|
||||
.map { ExtensionStoreType(it) }
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,9 @@ import graphql.schema.DataFetchingEnvironment
|
||||
import org.jetbrains.exposed.v1.core.Column
|
||||
import org.jetbrains.exposed.v1.core.Op
|
||||
import org.jetbrains.exposed.v1.core.SortOrder
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.greater
|
||||
import org.jetbrains.exposed.v1.core.greaterEq
|
||||
import org.jetbrains.exposed.v1.core.less
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
@@ -24,7 +26,6 @@ import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
|
||||
import suwayomi.tachidesk.graphql.queries.filter.LongFilter
|
||||
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.andFilterWithCompareEntity
|
||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString
|
||||
import suwayomi.tachidesk.graphql.queries.filter.applyOps
|
||||
@@ -37,6 +38,7 @@ import suwayomi.tachidesk.graphql.server.primitives.applyBeforeAfter
|
||||
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.ContentRating
|
||||
import suwayomi.tachidesk.graphql.types.SourceNodeList
|
||||
import suwayomi.tachidesk.graphql.types.SourceType
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
@@ -91,14 +93,17 @@ class SourceQuery {
|
||||
val id: Long? = null,
|
||||
val name: String? = null,
|
||||
val lang: String? = null,
|
||||
@GraphQLDeprecated("replace with contentRating == 3", ReplaceWith("contentRating"))
|
||||
val isNsfw: Boolean? = null,
|
||||
val contentRating: ContentRating? = null,
|
||||
) : HasGetOp {
|
||||
override fun getOp(): Op<Boolean>? {
|
||||
val opAnd = OpAnd()
|
||||
opAnd.eq(id, SourceTable.id)
|
||||
opAnd.eq(name, SourceTable.name)
|
||||
opAnd.eq(lang, SourceTable.lang)
|
||||
opAnd.eq(isNsfw, SourceTable.isNsfw)
|
||||
opAnd.andWhere(isNsfw) { if (it) SourceTable.contentRating greaterEq 3 else SourceTable.contentRating less 3 }
|
||||
opAnd.andWhere(contentRating) { SourceTable.contentRating eq it.getValue() }
|
||||
|
||||
return opAnd.op
|
||||
}
|
||||
@@ -108,7 +113,9 @@ class SourceQuery {
|
||||
val id: LongFilter? = null,
|
||||
val name: StringFilter? = null,
|
||||
val lang: StringFilter? = null,
|
||||
@GraphQLDeprecated("replace with contentRating == 3", ReplaceWith("contentRating"))
|
||||
val isNsfw: BooleanFilter? = null,
|
||||
// val contentRating: EnumFilter<ContentRating>? = null,
|
||||
override val and: List<SourceFilter>? = null,
|
||||
override val or: List<SourceFilter>? = null,
|
||||
override val not: SourceFilter? = null,
|
||||
@@ -118,7 +125,7 @@ class SourceQuery {
|
||||
andFilterWithCompareEntity(SourceTable.id, id),
|
||||
andFilterWithCompareString(SourceTable.name, name),
|
||||
andFilterWithCompareString(SourceTable.lang, lang),
|
||||
andFilterWithCompare(SourceTable.isNsfw, isNsfw),
|
||||
// andFilterWithCompareEnum(SourceTable.contentRating, contentRating)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -329,6 +329,24 @@ data class DoubleFilter(
|
||||
)
|
||||
}
|
||||
|
||||
data class EnumFilter<T : Enum<T>>(
|
||||
override val isNull: Boolean? = null,
|
||||
override val equalTo: T? = null,
|
||||
override val notEqualTo: T? = null,
|
||||
override val notEqualToAll: List<T>? = null,
|
||||
override val notEqualToAny: List<T>? = null,
|
||||
override val distinctFrom: T? = null,
|
||||
override val distinctFromAll: List<T>? = null,
|
||||
override val distinctFromAny: List<T>? = null,
|
||||
override val notDistinctFrom: T? = null,
|
||||
override val `in`: List<T>? = null,
|
||||
override val notIn: List<T>? = null,
|
||||
override val lessThan: T? = null,
|
||||
override val lessThanOrEqualTo: T? = null,
|
||||
override val greaterThan: T? = null,
|
||||
override val greaterThanOrEqualTo: T? = null,
|
||||
) : ComparableScalarFilter<T>
|
||||
|
||||
data class StringFilter(
|
||||
override val isNull: Boolean? = null,
|
||||
override val equalTo: String? = null,
|
||||
@@ -618,6 +636,35 @@ fun <T : Comparable<T>, S : T?> andFilterWithCompare(
|
||||
return opAnd.op
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Enum<T>> andFilterWithCompareEnum(
|
||||
column: Column<Int>,
|
||||
filter: ComparableScalarFilter<T>?,
|
||||
): Op<Boolean>? {
|
||||
filter ?: return null
|
||||
val opAnd = OpAnd()
|
||||
|
||||
opAnd.andWhere(filter.lessThan) { column less it.ordinal }
|
||||
opAnd.andWhere(filter.lessThanOrEqualTo) { column lessEq it.ordinal }
|
||||
opAnd.andWhere(filter.greaterThan) { column greater it.ordinal }
|
||||
opAnd.andWhere(filter.greaterThanOrEqualTo) { column greaterEq it.ordinal }
|
||||
|
||||
opAnd.andWhere(filter.isNull) { if (it) column.isNull() else column.isNotNull() }
|
||||
|
||||
opAnd.andWhere(filter.equalTo) { column eq it.ordinal }
|
||||
opAnd.andNotWhere(filter.notEqualTo, filter.notEqualToAll, filter.notEqualToAny) { column neq it.ordinal }
|
||||
opAnd.andWhere(filter.distinctFrom, filter.distinctFromAll, filter.distinctFromAny) { DistinctFromOp.distinctFrom(column, it.ordinal) }
|
||||
opAnd.andWhere(filter.notDistinctFrom) { DistinctFromOp.notDistinctFrom(column, it.ordinal) }
|
||||
if (!filter.`in`.isNullOrEmpty()) {
|
||||
opAnd.andWhere(filter.`in`) { column inList it.map { it.ordinal } }
|
||||
}
|
||||
if (!filter.notIn.isNullOrEmpty()) {
|
||||
opAnd.andWhere(filter.notIn) { column notInList it.map { it.ordinal } }
|
||||
}
|
||||
|
||||
return opAnd.op
|
||||
}
|
||||
|
||||
fun <T : Comparable<T>> andFilterWithCompareEntity(
|
||||
column: Column<EntityID<T>>,
|
||||
filter: ComparableScalarFilter<T>?,
|
||||
|
||||
@@ -20,7 +20,9 @@ import suwayomi.tachidesk.graphql.dataLoaders.DisplayScoreForTrackRecordDataLoad
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.DisplayScoreForTrackSearchDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.DownloadedChapterCountForMangaDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForExtensionStore
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForSourceDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionStoreForExtension
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.FirstUnreadChapterForMangaDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.GlobalMetaDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.HasDuplicateChaptersForMangaDataLoader
|
||||
@@ -78,6 +80,8 @@ class TachideskDataLoaderRegistryFactory {
|
||||
SourceMetaDataLoader(),
|
||||
ExtensionDataLoader(),
|
||||
ExtensionForSourceDataLoader(),
|
||||
ExtensionForExtensionStore(),
|
||||
ExtensionStoreForExtension(),
|
||||
TrackerDataLoader(),
|
||||
TrackerStatusesDataLoader(),
|
||||
TrackerScoresDataLoader(),
|
||||
|
||||
@@ -20,6 +20,7 @@ import suwayomi.tachidesk.graphql.mutations.CategoryMutation
|
||||
import suwayomi.tachidesk.graphql.mutations.ChapterMutation
|
||||
import suwayomi.tachidesk.graphql.mutations.DownloadMutation
|
||||
import suwayomi.tachidesk.graphql.mutations.ExtensionMutation
|
||||
import suwayomi.tachidesk.graphql.mutations.ExtensionStoreMutation
|
||||
import suwayomi.tachidesk.graphql.mutations.ImageMutation
|
||||
import suwayomi.tachidesk.graphql.mutations.InfoMutation
|
||||
import suwayomi.tachidesk.graphql.mutations.KoreaderSyncMutation
|
||||
@@ -36,6 +37,7 @@ import suwayomi.tachidesk.graphql.queries.CategoryQuery
|
||||
import suwayomi.tachidesk.graphql.queries.ChapterQuery
|
||||
import suwayomi.tachidesk.graphql.queries.DownloadQuery
|
||||
import suwayomi.tachidesk.graphql.queries.ExtensionQuery
|
||||
import suwayomi.tachidesk.graphql.queries.ExtensionStoreQuery
|
||||
import suwayomi.tachidesk.graphql.queries.InfoQuery
|
||||
import suwayomi.tachidesk.graphql.queries.KoreaderSyncQuery
|
||||
import suwayomi.tachidesk.graphql.queries.MangaQuery
|
||||
@@ -95,6 +97,7 @@ val schema =
|
||||
TopLevelObject(ChapterQuery()),
|
||||
TopLevelObject(DownloadQuery()),
|
||||
TopLevelObject(ExtensionQuery()),
|
||||
TopLevelObject(ExtensionStoreQuery()),
|
||||
TopLevelObject(InfoQuery()),
|
||||
TopLevelObject(KoreaderSyncQuery()),
|
||||
TopLevelObject(MangaQuery()),
|
||||
@@ -112,6 +115,7 @@ val schema =
|
||||
TopLevelObject(ChapterMutation()),
|
||||
TopLevelObject(DownloadMutation()),
|
||||
TopLevelObject(ExtensionMutation()),
|
||||
TopLevelObject(ExtensionStoreMutation()),
|
||||
TopLevelObject(ImageMutation()),
|
||||
TopLevelObject(InfoMutation()),
|
||||
TopLevelObject(KoreaderSyncMutation()),
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package suwayomi.tachidesk.graphql.types
|
||||
|
||||
/*
|
||||
* 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/. */
|
||||
|
||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||
import graphql.schema.DataFetchingEnvironment
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import suwayomi.tachidesk.graphql.server.primitives.Node
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionStoreTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ExtensionStoreType(
|
||||
val name: String,
|
||||
val badgeLabel: String,
|
||||
val signingKey: String,
|
||||
val contactWebsite: String,
|
||||
val contactDiscord: String?,
|
||||
val indexUrl: String,
|
||||
val isLegacy: Boolean,
|
||||
) : Node {
|
||||
constructor(row: ResultRow) : this(
|
||||
name = row[ExtensionStoreTable.name],
|
||||
badgeLabel = row[ExtensionStoreTable.badgeLabel],
|
||||
signingKey = row[ExtensionStoreTable.signingKey],
|
||||
contactWebsite = row[ExtensionStoreTable.contactWebsite],
|
||||
contactDiscord = row[ExtensionStoreTable.contactDiscord],
|
||||
indexUrl = row[ExtensionStoreTable.indexUrl],
|
||||
isLegacy = row[ExtensionStoreTable.isLegacy],
|
||||
)
|
||||
|
||||
fun extension(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionNodeList> =
|
||||
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionNodeList>("ExtensionForExtensionStore", indexUrl)
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
package suwayomi.tachidesk.graphql.types
|
||||
|
||||
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
||||
import com.expediagroup.graphql.generator.annotations.GraphQLDescription
|
||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||
import graphql.schema.DataFetchingEnvironment
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
@@ -20,29 +22,46 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ExtensionType(
|
||||
val storeIndexUrl: String?,
|
||||
@GraphQLDeprecated("Removed in extension api v1.6", ReplaceWith("storeIndexUrl"))
|
||||
val repo: String?,
|
||||
val apkName: String,
|
||||
@GraphQLDescription("This will be nullable in the future")
|
||||
val apkName: String?,
|
||||
val iconUrl: String,
|
||||
val name: String,
|
||||
val pkgName: String,
|
||||
val apkUrl: String?,
|
||||
val extensionLib: String?,
|
||||
val versionName: String,
|
||||
@GraphQLDeprecated(
|
||||
"Type was changed to Long, will be switched back to this variable name in the future.",
|
||||
ReplaceWith("versionCodeLong"),
|
||||
)
|
||||
val versionCode: Int,
|
||||
val versionCodeLong: Long,
|
||||
val lang: String,
|
||||
@GraphQLDeprecated("Removed in extension api v1.6", ReplaceWith("contentRating"))
|
||||
val isNsfw: Boolean,
|
||||
val contentRating: ContentRating,
|
||||
val isInstalled: Boolean,
|
||||
val hasUpdate: Boolean,
|
||||
val isObsolete: Boolean,
|
||||
) : Node {
|
||||
constructor(row: ResultRow) : this(
|
||||
repo = row[ExtensionTable.repo],
|
||||
apkName = row[ExtensionTable.apkName],
|
||||
iconUrl = Extension.getExtensionIconUrl(row[ExtensionTable.apkName]),
|
||||
storeIndexUrl = row[ExtensionTable.storeIndexUrl],
|
||||
repo = row[ExtensionTable.storeIndexUrl],
|
||||
apkName = row[ExtensionTable.apkName].orEmpty(),
|
||||
iconUrl = Extension.proxyExtensionIconUrl(row[ExtensionTable.pkgName]),
|
||||
name = row[ExtensionTable.name],
|
||||
pkgName = row[ExtensionTable.pkgName],
|
||||
apkUrl = row[ExtensionTable.apkUrl],
|
||||
extensionLib = row[ExtensionTable.extensionLib],
|
||||
versionName = row[ExtensionTable.versionName],
|
||||
versionCode = row[ExtensionTable.versionCode],
|
||||
versionCode = row[ExtensionTable.versionCode].toInt(),
|
||||
versionCodeLong = row[ExtensionTable.versionCode],
|
||||
lang = row[ExtensionTable.lang],
|
||||
isNsfw = row[ExtensionTable.isNsfw],
|
||||
isNsfw = row[ExtensionTable.contentRating] == 3,
|
||||
contentRating = ContentRating.valueOf(row[ExtensionTable.contentRating]),
|
||||
isInstalled = row[ExtensionTable.isInstalled],
|
||||
hasUpdate = row[ExtensionTable.hasUpdate],
|
||||
isObsolete = row[ExtensionTable.isObsolete],
|
||||
@@ -50,6 +69,9 @@ class ExtensionType(
|
||||
|
||||
fun source(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<SourceNodeList> =
|
||||
dataFetchingEnvironment.getValueFromDataLoader<String, SourceNodeList>("SourcesForExtensionDataLoader", pkgName)
|
||||
|
||||
fun extensionStore(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionStoreType> =
|
||||
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionStoreType>("ExtensionStoreForExtension", storeIndexUrl.orEmpty())
|
||||
}
|
||||
|
||||
data class ExtensionNodeList(
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
package suwayomi.tachidesk.graphql.types
|
||||
|
||||
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
||||
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
|
||||
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
@@ -25,7 +27,6 @@ import suwayomi.tachidesk.manga.impl.Source.getSourcePreferencesRaw
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
@@ -41,35 +42,31 @@ class SourceType(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val lang: String,
|
||||
val message: String?,
|
||||
val contentRating: ContentRating,
|
||||
val iconUrl: String,
|
||||
val supportsLatest: Boolean,
|
||||
val isConfigurable: Boolean,
|
||||
@GraphQLDeprecated("", ReplaceWith("contentRating"))
|
||||
val isNsfw: Boolean,
|
||||
val displayName: String,
|
||||
val homeUrl: String?,
|
||||
@GraphQLDeprecated("", ReplaceWith("homeUrl"))
|
||||
val baseUrl: String?,
|
||||
) : Node {
|
||||
constructor(source: SourceDataClass) : this(
|
||||
id = source.id.toLong(),
|
||||
name = source.name,
|
||||
lang = source.lang,
|
||||
iconUrl = source.iconUrl,
|
||||
supportsLatest = source.supportsLatest,
|
||||
isConfigurable = source.isConfigurable,
|
||||
isNsfw = source.isNsfw,
|
||||
displayName = source.displayName,
|
||||
baseUrl = source.baseUrl,
|
||||
)
|
||||
|
||||
constructor(row: ResultRow, sourceExtension: ResultRow, catalogueSource: CatalogueSource) : this(
|
||||
id = row[SourceTable.id].value,
|
||||
name = row[SourceTable.name],
|
||||
lang = row[SourceTable.lang],
|
||||
iconUrl = Extension.getExtensionIconUrl(sourceExtension[ExtensionTable.apkName]),
|
||||
message = row[SourceTable.message],
|
||||
contentRating = ContentRating.valueOf(row[SourceTable.contentRating]),
|
||||
iconUrl = Extension.proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]),
|
||||
supportsLatest = catalogueSource.supportsLatest,
|
||||
isConfigurable = catalogueSource is ConfigurableSource,
|
||||
isNsfw = row[SourceTable.isNsfw],
|
||||
isNsfw = row[SourceTable.contentRating] >= 3,
|
||||
displayName = catalogueSource.toString(),
|
||||
baseUrl = catalogueSource.runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
|
||||
homeUrl = runCatching { (catalogueSource as? HttpSource)?.getHomeUrl() }.getOrNull(),
|
||||
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
|
||||
)
|
||||
|
||||
fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaNodeList> =
|
||||
@@ -510,3 +507,33 @@ fun preferenceOf(preference: SourcePreference): Preference =
|
||||
throw RuntimeException("sealed class cannot have more subtypes!")
|
||||
}
|
||||
}
|
||||
|
||||
enum class ContentRating {
|
||||
SAFE,
|
||||
SUGGESTIVE,
|
||||
EROTICA,
|
||||
PORNOGRAPHIC,
|
||||
;
|
||||
|
||||
@GraphQLIgnore
|
||||
fun getValue() =
|
||||
when (this) {
|
||||
SAFE -> 0
|
||||
SUGGESTIVE -> 1
|
||||
EROTICA -> 2
|
||||
PORNOGRAPHIC -> 3
|
||||
}
|
||||
|
||||
@GraphQLIgnore
|
||||
companion object {
|
||||
@GraphQLIgnore
|
||||
fun valueOf(contentRating: Int) =
|
||||
when (contentRating) {
|
||||
0 -> SAFE
|
||||
1 -> SUGGESTIVE
|
||||
2 -> EROTICA
|
||||
3 -> PORNOGRAPHIC
|
||||
else -> SAFE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user