mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-05 03:44:36 -05:00
Compare commits
4 Commits
extensions
...
renovate/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5561761020 | ||
|
|
a210153ed1 | ||
|
|
b40447c4f9 | ||
|
|
193dd1ee84 |
@@ -12,7 +12,7 @@ dex2jar = "2.4.36"
|
|||||||
polyglot = "25.0.3"
|
polyglot = "25.0.3"
|
||||||
settings = "1.3.0"
|
settings = "1.3.0"
|
||||||
twelvemonkeys = "3.13.1"
|
twelvemonkeys = "3.13.1"
|
||||||
graphqlkotlin = "8.9.0"
|
graphqlkotlin = "10.0.0-alpha.3"
|
||||||
xmlserialization = "0.91.3"
|
xmlserialization = "0.91.3"
|
||||||
ktlint = "1.8.0"
|
ktlint = "1.8.0"
|
||||||
koin = "4.2.1"
|
koin = "4.2.1"
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package suwayomi.tachidesk.graphql
|
|
||||||
|
|
||||||
import com.expediagroup.graphql.server.extensions.toGraphQLError
|
|
||||||
import graphql.execution.DataFetcherResult
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
|
||||||
|
|
||||||
val logger = KotlinLogging.logger { }
|
|
||||||
|
|
||||||
inline fun <T> asDataFetcherResult(block: () -> T): DataFetcherResult<T?> {
|
|
||||||
val result =
|
|
||||||
runCatching {
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.isFailure) {
|
|
||||||
logger.error(result.exceptionOrNull()) { "asDataFetcherResult: failed due to" }
|
|
||||||
return DataFetcherResult
|
|
||||||
.newResult<T?>()
|
|
||||||
.error(result.exceptionOrNull()?.toGraphQLError())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
return DataFetcherResult
|
|
||||||
.newResult<T?>()
|
|
||||||
.data(result.getOrNull())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
@@ -3,12 +3,8 @@ package suwayomi.tachidesk.graphql.cache
|
|||||||
import org.dataloader.CacheMap
|
import org.dataloader.CacheMap
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
class CustomCacheMap<K, V> : CacheMap<K, V> {
|
class CustomCacheMap<K : Any, V : Any> : CacheMap<K, V> {
|
||||||
private val cache: MutableMap<K, CompletableFuture<V>>
|
private val cache: MutableMap<K, CompletableFuture<V>> = HashMap()
|
||||||
|
|
||||||
init {
|
|
||||||
cache = HashMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun containsKey(key: K): Boolean = cache.containsKey(key)
|
override fun containsKey(key: K): Boolean = cache.containsKey(key)
|
||||||
|
|
||||||
@@ -18,12 +14,12 @@ class CustomCacheMap<K, V> : CacheMap<K, V> {
|
|||||||
|
|
||||||
override fun getAll(): Collection<CompletableFuture<V>> = cache.values
|
override fun getAll(): Collection<CompletableFuture<V>> = cache.values
|
||||||
|
|
||||||
override fun set(
|
override fun putIfAbsentAtomically(
|
||||||
key: K,
|
key: K,
|
||||||
value: CompletableFuture<V>,
|
value: CompletableFuture<V>,
|
||||||
): CacheMap<K, V> {
|
): CompletableFuture<V> {
|
||||||
cache[key] = value
|
cache[key] = value
|
||||||
return this
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun delete(key: K): CacheMap<K, V> {
|
override fun delete(key: K): CacheMap<K, V> {
|
||||||
@@ -35,4 +31,6 @@ class CustomCacheMap<K, V> : CacheMap<K, V> {
|
|||||||
cache.clear()
|
cache.clear()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun size(): Int = cache.size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ import suwayomi.tachidesk.graphql.types.ChapterType
|
|||||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class ChapterDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class ChapterDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "ChapterDataLoader"
|
override val dataLoaderName = "ChapterDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -48,7 +48,7 @@ class ChaptersForMangaDataLoader : KotlinDataLoader<Int, ChapterNodeList> {
|
|||||||
override val dataLoaderName = "ChaptersForMangaDataLoader"
|
override val dataLoaderName = "ChaptersForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterNodeList> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterNodeList> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterNodeList> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -68,7 +68,7 @@ class DownloadedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
|||||||
override val dataLoaderName = "DownloadedChapterCountForMangaDataLoader"
|
override val dataLoaderName = "DownloadedChapterCountForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
||||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -90,7 +90,7 @@ class UnreadChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
|||||||
override val dataLoaderName = "UnreadChapterCountForMangaDataLoader"
|
override val dataLoaderName = "UnreadChapterCountForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
||||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -112,7 +112,7 @@ class BookmarkedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
|||||||
override val dataLoaderName = "BookmarkedChapterCountForMangaDataLoader"
|
override val dataLoaderName = "BookmarkedChapterCountForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
|
||||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -157,11 +157,11 @@ class HasDuplicateChaptersForMangaDataLoader : KotlinDataLoader<Int, Boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "LastReadChapterForMangaDataLoader"
|
override val dataLoaderName = "LastReadChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -177,11 +177,11 @@ class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LatestReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class LatestReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "LatestReadChapterForMangaDataLoader"
|
override val dataLoaderName = "LatestReadChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -197,11 +197,11 @@ class LatestReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LatestFetchedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class LatestFetchedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "LatestFetchedChapterForMangaDataLoader"
|
override val dataLoaderName = "LatestFetchedChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -217,11 +217,11 @@ class LatestFetchedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "LatestUploadedChapterForMangaDataLoader"
|
override val dataLoaderName = "LatestUploadedChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -237,11 +237,11 @@ class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterTyp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "FirstUnreadChapterForMangaDataLoader"
|
override val dataLoaderName = "FirstUnreadChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
@@ -257,11 +257,11 @@ class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HighestNumberedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
class HighestNumberedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
|
||||||
override val dataLoaderName = "HighestNumberedChapterForMangaDataLoader"
|
override val dataLoaderName = "HighestNumberedChapterForMangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
|
||||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType?> {
|
class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType> {
|
||||||
override val dataLoaderName = "ExtensionDataLoader"
|
override val dataLoaderName = "ExtensionDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionType> =
|
||||||
DataLoaderFactory.newDataLoader { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
@@ -40,10 +40,10 @@ class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType?> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExtensionForSourceDataLoader : KotlinDataLoader<Long, ExtensionType?> {
|
class ExtensionForSourceDataLoader : KotlinDataLoader<Long, ExtensionType> {
|
||||||
override val dataLoaderName = "ExtensionForSourceDataLoader"
|
override val dataLoaderName = "ExtensionForSourceDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, ExtensionType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, ExtensionType> =
|
||||||
DataLoaderFactory.newDataLoader { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class MangaDataLoader : KotlinDataLoader<Int, MangaType?> {
|
class MangaDataLoader : KotlinDataLoader<Int, MangaType> {
|
||||||
override val dataLoaderName = "MangaDataLoader"
|
override val dataLoaderName = "MangaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, MangaType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, MangaType> =
|
||||||
DataLoaderFactory.newDataLoader { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
@@ -122,6 +122,6 @@ class MangaForIdsDataLoader : KotlinDataLoader<List<Int>, MangaNodeList> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DataLoaderOptions.newOptions().setCacheMap(CustomCacheMap<List<Int>, MangaNodeList>()),
|
DataLoaderOptions.newOptions().setCacheMap(CustomCacheMap<List<Int>, MangaNodeList>()).build(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
|
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class GlobalMetaDataLoader : KotlinDataLoader<String, GlobalMetaType?> {
|
class GlobalMetaDataLoader : KotlinDataLoader<String, GlobalMetaType> {
|
||||||
override val dataLoaderName = "GlobalMetaDataLoader"
|
override val dataLoaderName = "GlobalMetaDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, GlobalMetaType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, GlobalMetaType> =
|
||||||
DataLoaderFactory.newDataLoader<String, GlobalMetaType?> { ids ->
|
DataLoaderFactory.newDataLoader<String, GlobalMetaType> { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
addLogger(Slf4jSqlDebugLogger)
|
addLogger(Slf4jSqlDebugLogger)
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
|||||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
|
||||||
class SourceDataLoader : KotlinDataLoader<Long, SourceType?> {
|
class SourceDataLoader : KotlinDataLoader<Long, SourceType> {
|
||||||
override val dataLoaderName = "SourceDataLoader"
|
override val dataLoaderName = "SourceDataLoader"
|
||||||
|
|
||||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, SourceType?> =
|
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, SourceType> =
|
||||||
DataLoaderFactory.newDataLoader { ids ->
|
DataLoaderFactory.newDataLoader { ids ->
|
||||||
future {
|
future {
|
||||||
transaction {
|
transaction {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
@@ -15,7 +17,6 @@ import org.jetbrains.exposed.sql.or
|
|||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.CategoryMetaType
|
import suwayomi.tachidesk.graphql.types.CategoryMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.CategoryType
|
import suwayomi.tachidesk.graphql.types.CategoryType
|
||||||
@@ -42,14 +43,13 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setCategoryMeta(input: SetCategoryMetaInput): DataFetcherResult<SetCategoryMetaPayload?> =
|
fun setCategoryMeta(input: SetCategoryMetaInput): SetCategoryMetaPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, meta) = input
|
||||||
val (clientMutationId, meta) = input
|
|
||||||
|
|
||||||
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
|
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
|
||||||
|
|
||||||
SetCategoryMetaPayload(clientMutationId, meta)
|
return SetCategoryMetaPayload(clientMutationId, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteCategoryMetaInput(
|
data class DeleteCategoryMetaInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -64,34 +64,33 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DataFetcherResult<DeleteCategoryMetaPayload?> =
|
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DeleteCategoryMetaPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, categoryId, key) = input
|
||||||
val (clientMutationId, categoryId, key) = input
|
|
||||||
|
|
||||||
val (meta, category) =
|
val (meta, category) =
|
||||||
transaction {
|
transaction {
|
||||||
val meta =
|
val meta =
|
||||||
CategoryMetaTable
|
CategoryMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
.where { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
|
||||||
CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||||
|
|
||||||
val category =
|
val category =
|
||||||
transaction {
|
transaction {
|
||||||
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq categoryId }.first())
|
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq categoryId }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta != null) {
|
if (meta != null) {
|
||||||
CategoryMetaType(meta)
|
CategoryMetaType(meta)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
} to category
|
} to category
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
return DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetCategoryMetasItem(
|
data class SetCategoryMetasItem(
|
||||||
val categoryIds: List<Int>,
|
val categoryIds: List<Int>,
|
||||||
@@ -110,43 +109,42 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setCategoryMetas(input: SetCategoryMetasInput): DataFetcherResult<SetCategoryMetasPayload?> =
|
fun setCategoryMetas(input: SetCategoryMetasInput): SetCategoryMetasPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, items) = input
|
||||||
val (clientMutationId, items) = input
|
|
||||||
|
|
||||||
val metaByCategoryId =
|
val metaByCategoryId =
|
||||||
items
|
items
|
||||||
.flatMap { item ->
|
.flatMap { item ->
|
||||||
val metaMap = item.metas.associate { it.key to it.value }
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
item.categoryIds.map { categoryId -> categoryId to metaMap }
|
item.categoryIds.map { categoryId -> categoryId to metaMap }
|
||||||
}.groupBy({ it.first }, { it.second })
|
}.groupBy({ it.first }, { it.second })
|
||||||
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
|
|
||||||
Category.modifyCategoriesMetas(metaByCategoryId)
|
Category.modifyCategoriesMetas(metaByCategoryId)
|
||||||
|
|
||||||
val allCategoryIds = metaByCategoryId.keys
|
val allCategoryIds = metaByCategoryId.keys
|
||||||
val allMetaKeys = metaByCategoryId.values.flatMap { item -> item.keys }.distinct()
|
val allMetaKeys = metaByCategoryId.values.flatMap { item -> item.keys }.distinct()
|
||||||
|
|
||||||
val (updatedMetas, categories) =
|
val (updatedMetas, categories) =
|
||||||
transaction {
|
transaction {
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
CategoryMetaTable
|
CategoryMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (CategoryMetaTable.ref inList allCategoryIds) and (CategoryMetaTable.key inList allMetaKeys) }
|
.where { (CategoryMetaTable.ref inList allCategoryIds) and (CategoryMetaTable.key inList allMetaKeys) }
|
||||||
.map { CategoryMetaType(it) }
|
.map { CategoryMetaType(it) }
|
||||||
|
|
||||||
val categories =
|
val categories =
|
||||||
CategoryTable
|
CategoryTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { CategoryTable.id inList allCategoryIds }
|
.where { CategoryTable.id inList allCategoryIds }
|
||||||
.map { CategoryType(it) }
|
.map { CategoryType(it) }
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
|
|
||||||
updatedMetas to categories
|
updatedMetas to categories
|
||||||
}
|
}
|
||||||
|
|
||||||
SetCategoryMetasPayload(clientMutationId, updatedMetas, categories)
|
return SetCategoryMetasPayload(clientMutationId, updatedMetas, categories)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteCategoryMetasItem(
|
data class DeleteCategoryMetasItem(
|
||||||
val categoryIds: List<Int>,
|
val categoryIds: List<Int>,
|
||||||
@@ -166,64 +164,63 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteCategoryMetas(input: DeleteCategoryMetasInput): DataFetcherResult<DeleteCategoryMetasPayload?> =
|
fun deleteCategoryMetas(input: DeleteCategoryMetasInput): DeleteCategoryMetasPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, items) = input
|
||||||
val (clientMutationId, items) = input
|
|
||||||
|
|
||||||
items.forEach { item ->
|
items.forEach { item ->
|
||||||
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
"Either 'keys' or 'prefixes' must be provided for each item"
|
"Either 'keys' or 'prefixes' must be provided for each item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (allDeletedMetas, allCategoryIds) =
|
||||||
|
transaction {
|
||||||
|
val deletedMetas = mutableListOf<CategoryMetaType>()
|
||||||
|
val categoryIds = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
val keyCondition: Op<Boolean>? =
|
||||||
|
item.keys?.takeIf { it.isNotEmpty() }?.let { CategoryMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
item.prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (CategoryMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val metaKeyCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val condition = (CategoryMetaTable.ref inList item.categoryIds) and metaKeyCondition
|
||||||
|
|
||||||
|
deletedMetas +=
|
||||||
|
CategoryMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { condition }
|
||||||
|
.map { CategoryMetaType(it) }
|
||||||
|
|
||||||
|
CategoryMetaTable.deleteWhere { condition }
|
||||||
|
categoryIds += item.categoryIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletedMetas to categoryIds
|
||||||
}
|
}
|
||||||
|
|
||||||
val (allDeletedMetas, allCategoryIds) =
|
val categories =
|
||||||
transaction {
|
transaction {
|
||||||
val deletedMetas = mutableListOf<CategoryMetaType>()
|
CategoryTable
|
||||||
val categoryIds = mutableSetOf<Int>()
|
.selectAll()
|
||||||
|
.where { CategoryTable.id inList allCategoryIds }
|
||||||
|
.map { CategoryType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
items.forEach { item ->
|
return DeleteCategoryMetasPayload(clientMutationId, allDeletedMetas, categories)
|
||||||
val keyCondition: Op<Boolean>? =
|
}
|
||||||
item.keys?.takeIf { it.isNotEmpty() }?.let { CategoryMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
item.prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (CategoryMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val metaKeyCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val condition = (CategoryMetaTable.ref inList item.categoryIds) and metaKeyCondition
|
|
||||||
|
|
||||||
deletedMetas +=
|
|
||||||
CategoryMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { condition }
|
|
||||||
.map { CategoryMetaType(it) }
|
|
||||||
|
|
||||||
CategoryMetaTable.deleteWhere { condition }
|
|
||||||
categoryIds += item.categoryIds
|
|
||||||
}
|
|
||||||
|
|
||||||
deletedMetas to categoryIds
|
|
||||||
}
|
|
||||||
|
|
||||||
val categories =
|
|
||||||
transaction {
|
|
||||||
CategoryTable
|
|
||||||
.selectAll()
|
|
||||||
.where { CategoryTable.id inList allCategoryIds }
|
|
||||||
.map { CategoryType(it) }
|
|
||||||
.distinctBy { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteCategoryMetasPayload(clientMutationId, allDeletedMetas, categories)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class UpdateCategoryPatch(
|
data class UpdateCategoryPatch(
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
@@ -291,40 +288,38 @@ class CategoryMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateCategory(input: UpdateCategoryInput): DataFetcherResult<UpdateCategoryPayload?> =
|
fun updateCategory(input: UpdateCategoryInput): UpdateCategoryPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, id, patch) = input
|
||||||
val (clientMutationId, id, patch) = input
|
|
||||||
|
|
||||||
updateCategories(listOf(id), patch)
|
updateCategories(listOf(id), patch)
|
||||||
|
|
||||||
val category =
|
val category =
|
||||||
transaction {
|
transaction {
|
||||||
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
|
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateCategoryPayload(
|
return UpdateCategoryPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
category = category,
|
category = category,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateCategories(input: UpdateCategoriesInput): DataFetcherResult<UpdateCategoriesPayload?> =
|
fun updateCategories(input: UpdateCategoriesInput): UpdateCategoriesPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, ids, patch) = input
|
||||||
val (clientMutationId, ids, patch) = input
|
|
||||||
|
|
||||||
updateCategories(ids, patch)
|
updateCategories(ids, patch)
|
||||||
|
|
||||||
val categories =
|
val categories =
|
||||||
transaction {
|
transaction {
|
||||||
CategoryTable.selectAll().where { CategoryTable.id inList ids }.map { CategoryType(it) }
|
CategoryTable.selectAll().where { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateCategoriesPayload(
|
return UpdateCategoriesPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
categories = categories,
|
categories = categories,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class UpdateCategoryOrderPayload(
|
data class UpdateCategoryOrderPayload(
|
||||||
val clientMutationId: String?,
|
val clientMutationId: String?,
|
||||||
@@ -338,50 +333,49 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateCategoryOrder(input: UpdateCategoryOrderInput): DataFetcherResult<UpdateCategoryOrderPayload?> =
|
fun updateCategoryOrder(input: UpdateCategoryOrderInput): UpdateCategoryOrderPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, categoryId, position) = input
|
||||||
val (clientMutationId, categoryId, position) = input
|
require(position > 0) {
|
||||||
require(position > 0) {
|
"'order' must not be <= 0"
|
||||||
"'order' must not be <= 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction {
|
|
||||||
val currentOrder =
|
|
||||||
CategoryTable
|
|
||||||
.selectAll()
|
|
||||||
.where { CategoryTable.id eq categoryId }
|
|
||||||
.first()[CategoryTable.order]
|
|
||||||
|
|
||||||
if (currentOrder != position) {
|
|
||||||
if (position < currentOrder) {
|
|
||||||
CategoryTable.update({ CategoryTable.order greaterEq position }) {
|
|
||||||
it[CategoryTable.order] = CategoryTable.order + 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CategoryTable.update({ CategoryTable.order lessEq position }) {
|
|
||||||
it[CategoryTable.order] = CategoryTable.order - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
|
||||||
it[CategoryTable.order] = position
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Category.normalizeCategories()
|
|
||||||
|
|
||||||
val categories =
|
|
||||||
transaction {
|
|
||||||
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateCategoryOrderPayload(
|
|
||||||
clientMutationId = clientMutationId,
|
|
||||||
categories = categories,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
val currentOrder =
|
||||||
|
CategoryTable
|
||||||
|
.selectAll()
|
||||||
|
.where { CategoryTable.id eq categoryId }
|
||||||
|
.first()[CategoryTable.order]
|
||||||
|
|
||||||
|
if (currentOrder != position) {
|
||||||
|
if (position < currentOrder) {
|
||||||
|
CategoryTable.update({ CategoryTable.order greaterEq position }) {
|
||||||
|
it[CategoryTable.order] = CategoryTable.order + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CategoryTable.update({ CategoryTable.order lessEq position }) {
|
||||||
|
it[CategoryTable.order] = CategoryTable.order - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
||||||
|
it[CategoryTable.order] = position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Category.normalizeCategories()
|
||||||
|
|
||||||
|
val categories =
|
||||||
|
transaction {
|
||||||
|
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return UpdateCategoryOrderPayload(
|
||||||
|
clientMutationId = clientMutationId,
|
||||||
|
categories = categories,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
data class CreateCategoryInput(
|
data class CreateCategoryInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
val name: String,
|
val name: String,
|
||||||
@@ -397,53 +391,52 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun createCategory(input: CreateCategoryInput): DataFetcherResult<CreateCategoryPayload?> =
|
fun createCategory(input: CreateCategoryInput): CreateCategoryPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, name, order, default, includeInUpdate, includeInDownload) = input
|
||||||
val (clientMutationId, name, order, default, includeInUpdate, includeInDownload) = input
|
transaction {
|
||||||
transaction {
|
require(CategoryTable.selectAll().where { CategoryTable.name eq input.name }.isEmpty()) {
|
||||||
require(CategoryTable.selectAll().where { CategoryTable.name eq input.name }.isEmpty()) {
|
"'name' must be unique"
|
||||||
"'name' must be unique"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
require(!name.equals(Category.DEFAULT_CATEGORY_NAME, ignoreCase = true)) {
|
}
|
||||||
"'name' must not be ${Category.DEFAULT_CATEGORY_NAME}"
|
require(!name.equals(Category.DEFAULT_CATEGORY_NAME, ignoreCase = true)) {
|
||||||
}
|
"'name' must not be ${Category.DEFAULT_CATEGORY_NAME}"
|
||||||
if (order != null) {
|
}
|
||||||
require(order > 0) {
|
if (order != null) {
|
||||||
"'order' must not be <= 0"
|
require(order > 0) {
|
||||||
}
|
"'order' must not be <= 0"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val category =
|
val category =
|
||||||
transaction {
|
transaction {
|
||||||
if (order != null) {
|
if (order != null) {
|
||||||
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
||||||
it[CategoryTable.order] = CategoryTable.order + 1
|
it[CategoryTable.order] = CategoryTable.order + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val id =
|
||||||
|
CategoryTable.insertAndGetId {
|
||||||
|
it[CategoryTable.name] = input.name
|
||||||
|
it[CategoryTable.order] = order ?: Int.MAX_VALUE
|
||||||
|
if (default != null) {
|
||||||
|
it[CategoryTable.isDefault] = default
|
||||||
|
}
|
||||||
|
if (includeInUpdate != null) {
|
||||||
|
it[CategoryTable.includeInUpdate] = includeInUpdate.value
|
||||||
|
}
|
||||||
|
if (includeInDownload != null) {
|
||||||
|
it[CategoryTable.includeInDownload] = includeInDownload.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val id =
|
Category.normalizeCategories()
|
||||||
CategoryTable.insertAndGetId {
|
|
||||||
it[CategoryTable.name] = input.name
|
|
||||||
it[CategoryTable.order] = order ?: Int.MAX_VALUE
|
|
||||||
if (default != null) {
|
|
||||||
it[CategoryTable.isDefault] = default
|
|
||||||
}
|
|
||||||
if (includeInUpdate != null) {
|
|
||||||
it[CategoryTable.includeInUpdate] = includeInUpdate.value
|
|
||||||
}
|
|
||||||
if (includeInDownload != null) {
|
|
||||||
it[CategoryTable.includeInDownload] = includeInDownload.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Category.normalizeCategories()
|
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
|
||||||
|
}
|
||||||
|
|
||||||
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
|
return CreateCategoryPayload(clientMutationId, category)
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateCategoryPayload(clientMutationId, category)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class DeleteCategoryInput(
|
data class DeleteCategoryInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -457,47 +450,45 @@ class CategoryMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteCategory(input: DeleteCategoryInput): DataFetcherResult<DeleteCategoryPayload?> {
|
fun deleteCategory(input: DeleteCategoryInput): DeleteCategoryPayload? {
|
||||||
return asDataFetcherResult {
|
val (clientMutationId, categoryId) = input
|
||||||
val (clientMutationId, categoryId) = input
|
if (categoryId == 0) { // Don't delete default category
|
||||||
if (categoryId == 0) { // Don't delete default category
|
return DeleteCategoryPayload(
|
||||||
return@asDataFetcherResult DeleteCategoryPayload(
|
clientMutationId,
|
||||||
clientMutationId,
|
null,
|
||||||
null,
|
emptyList(),
|
||||||
emptyList(),
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
val (category, mangas) =
|
||||||
|
transaction {
|
||||||
|
val category =
|
||||||
|
CategoryTable
|
||||||
|
.selectAll()
|
||||||
|
.where { CategoryTable.id eq categoryId }
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
val mangas =
|
||||||
|
transaction {
|
||||||
|
MangaTable
|
||||||
|
.innerJoin(CategoryMangaTable)
|
||||||
|
.selectAll()
|
||||||
|
.where { CategoryMangaTable.category eq categoryId }
|
||||||
|
.map { MangaType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
||||||
|
|
||||||
|
Category.normalizeCategories()
|
||||||
|
|
||||||
|
if (category != null) {
|
||||||
|
CategoryType(category)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
} to mangas
|
||||||
}
|
}
|
||||||
|
|
||||||
val (category, mangas) =
|
return DeleteCategoryPayload(clientMutationId, category, mangas)
|
||||||
transaction {
|
|
||||||
val category =
|
|
||||||
CategoryTable
|
|
||||||
.selectAll()
|
|
||||||
.where { CategoryTable.id eq categoryId }
|
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
val mangas =
|
|
||||||
transaction {
|
|
||||||
MangaTable
|
|
||||||
.innerJoin(CategoryMangaTable)
|
|
||||||
.selectAll()
|
|
||||||
.where { CategoryMangaTable.category eq categoryId }
|
|
||||||
.map { MangaType(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
|
||||||
|
|
||||||
Category.normalizeCategories()
|
|
||||||
|
|
||||||
if (category != null) {
|
|
||||||
CategoryType(category)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
} to mangas
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteCategoryPayload(clientMutationId, category, mangas)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class UpdateMangaCategoriesPatch(
|
data class UpdateMangaCategoriesPatch(
|
||||||
@@ -547,38 +538,36 @@ class CategoryMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateMangaCategories(input: UpdateMangaCategoriesInput): DataFetcherResult<UpdateMangaCategoriesPayload?> =
|
fun updateMangaCategories(input: UpdateMangaCategoriesInput): UpdateMangaCategoriesPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, id, patch) = input
|
||||||
val (clientMutationId, id, patch) = input
|
|
||||||
|
|
||||||
updateMangas(listOf(id), patch)
|
updateMangas(listOf(id), patch)
|
||||||
|
|
||||||
val manga =
|
val manga =
|
||||||
transaction {
|
transaction {
|
||||||
MangaType(MangaTable.selectAll().where { MangaTable.id eq id }.first())
|
MangaType(MangaTable.selectAll().where { MangaTable.id eq id }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMangaCategoriesPayload(
|
return UpdateMangaCategoriesPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
manga = manga,
|
manga = manga,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateMangasCategories(input: UpdateMangasCategoriesInput): DataFetcherResult<UpdateMangasCategoriesPayload?> =
|
fun updateMangasCategories(input: UpdateMangasCategoriesInput): UpdateMangasCategoriesPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, ids, patch) = input
|
||||||
val (clientMutationId, ids, patch) = input
|
|
||||||
|
|
||||||
updateMangas(ids, patch)
|
updateMangas(ids, patch)
|
||||||
|
|
||||||
val mangas =
|
val mangas =
|
||||||
transaction {
|
transaction {
|
||||||
MangaTable.selectAll().where { MangaTable.id inList ids }.map { MangaType(it) }
|
MangaTable.selectAll().where { MangaTable.id inList ids }.map { MangaType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMangasCategoriesPayload(
|
return UpdateMangasCategoriesPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
mangas = mangas,
|
mangas = mangas,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
import org.jetbrains.exposed.dao.id.EntityID
|
||||||
@@ -16,7 +17,6 @@ import org.jetbrains.exposed.sql.selectAll
|
|||||||
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
|
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.ChapterMetaType
|
import suwayomi.tachidesk.graphql.types.ChapterMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||||
@@ -120,40 +120,38 @@ class ChapterMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateChapter(input: UpdateChapterInput): DataFetcherResult<UpdateChapterPayload?> =
|
fun updateChapter(input: UpdateChapterInput): UpdateChapterPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, id, patch) = input
|
||||||
val (clientMutationId, id, patch) = input
|
|
||||||
|
|
||||||
updateChapters(listOf(id), patch)
|
updateChapters(listOf(id), patch)
|
||||||
|
|
||||||
val chapter =
|
val chapter =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq id }.first())
|
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq id }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateChapterPayload(
|
return UpdateChapterPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapter = chapter,
|
chapter = chapter,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateChapters(input: UpdateChaptersInput): DataFetcherResult<UpdateChaptersPayload?> =
|
fun updateChapters(input: UpdateChaptersInput): UpdateChaptersPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, ids, patch) = input
|
||||||
val (clientMutationId, ids, patch) = input
|
|
||||||
|
|
||||||
updateChapters(ids, patch)
|
updateChapters(ids, patch)
|
||||||
|
|
||||||
val chapters =
|
val chapters =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable.selectAll().where { ChapterTable.id inList ids }.map { ChapterType(it) }
|
ChapterTable.selectAll().where { ChapterTable.id inList ids }.map { ChapterType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateChaptersPayload(
|
return UpdateChaptersPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapters = chapters,
|
chapters = chapters,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FetchChaptersInput(
|
data class FetchChaptersInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -166,27 +164,25 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<DataFetcherResult<FetchChaptersPayload?>> {
|
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<FetchChaptersPayload?> {
|
||||||
val (clientMutationId, mangaId) = input
|
val (clientMutationId, mangaId) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
Chapter.fetchChapterList(mangaId)
|
||||||
Chapter.fetchChapterList(mangaId)
|
|
||||||
|
|
||||||
val chapters =
|
val chapters =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable
|
ChapterTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ChapterTable.manga eq mangaId }
|
.where { ChapterTable.manga eq mangaId }
|
||||||
.orderBy(ChapterTable.sourceOrder)
|
.orderBy(ChapterTable.sourceOrder)
|
||||||
.map { ChapterType(it) }
|
.map { ChapterType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchChaptersPayload(
|
FetchChaptersPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapters = chapters,
|
chapters = chapters,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,14 +197,13 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setChapterMeta(input: SetChapterMetaInput): DataFetcherResult<SetChapterMetaPayload?> =
|
fun setChapterMeta(input: SetChapterMetaInput): SetChapterMetaPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, meta) = input
|
||||||
val (clientMutationId, meta) = input
|
|
||||||
|
|
||||||
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
|
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
|
||||||
|
|
||||||
SetChapterMetaPayload(clientMutationId, meta)
|
return SetChapterMetaPayload(clientMutationId, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteChapterMetaInput(
|
data class DeleteChapterMetaInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -223,34 +218,33 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteChapterMeta(input: DeleteChapterMetaInput): DataFetcherResult<DeleteChapterMetaPayload?> =
|
fun deleteChapterMeta(input: DeleteChapterMetaInput): DeleteChapterMetaPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, chapterId, key) = input
|
||||||
val (clientMutationId, chapterId, key) = input
|
|
||||||
|
|
||||||
val (meta, chapter) =
|
val (meta, chapter) =
|
||||||
transaction {
|
transaction {
|
||||||
val meta =
|
val meta =
|
||||||
ChapterMetaTable
|
ChapterMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
.where { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
|
||||||
ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||||
|
|
||||||
val chapter =
|
val chapter =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq chapterId }.first())
|
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq chapterId }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta != null) {
|
if (meta != null) {
|
||||||
ChapterMetaType(meta)
|
ChapterMetaType(meta)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
} to chapter
|
} to chapter
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteChapterMetaPayload(clientMutationId, meta, chapter)
|
return DeleteChapterMetaPayload(clientMutationId, meta, chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetChapterMetasItem(
|
data class SetChapterMetasItem(
|
||||||
val chapterIds: List<Int>,
|
val chapterIds: List<Int>,
|
||||||
@@ -269,43 +263,42 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setChapterMetas(input: SetChapterMetasInput): DataFetcherResult<SetChapterMetasPayload?> =
|
fun setChapterMetas(input: SetChapterMetasInput): SetChapterMetasPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, items) = input
|
||||||
val (clientMutationId, items) = input
|
|
||||||
|
|
||||||
val metaByChapterId =
|
val metaByChapterId =
|
||||||
items
|
items
|
||||||
.flatMap { item ->
|
.flatMap { item ->
|
||||||
val metaMap = item.metas.associate { it.key to it.value }
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
item.chapterIds.map { chapterId -> chapterId to metaMap }
|
item.chapterIds.map { chapterId -> chapterId to metaMap }
|
||||||
}.groupBy({ it.first }, { it.second })
|
}.groupBy({ it.first }, { it.second })
|
||||||
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
|
|
||||||
Chapter.modifyChaptersMetas(metaByChapterId)
|
Chapter.modifyChaptersMetas(metaByChapterId)
|
||||||
|
|
||||||
val allChapterIds = metaByChapterId.keys
|
val allChapterIds = metaByChapterId.keys
|
||||||
val allMetaKeys = metaByChapterId.values.flatMap { it.keys }.distinct()
|
val allMetaKeys = metaByChapterId.values.flatMap { it.keys }.distinct()
|
||||||
|
|
||||||
val (updatedMetas, chapters) =
|
val (updatedMetas, chapters) =
|
||||||
transaction {
|
transaction {
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
ChapterMetaTable
|
ChapterMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (ChapterMetaTable.ref inList allChapterIds) and (ChapterMetaTable.key inList allMetaKeys) }
|
.where { (ChapterMetaTable.ref inList allChapterIds) and (ChapterMetaTable.key inList allMetaKeys) }
|
||||||
.map { ChapterMetaType(it) }
|
.map { ChapterMetaType(it) }
|
||||||
|
|
||||||
val chapters =
|
val chapters =
|
||||||
ChapterTable
|
ChapterTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ChapterTable.id inList allChapterIds }
|
.where { ChapterTable.id inList allChapterIds }
|
||||||
.map { ChapterType(it) }
|
.map { ChapterType(it) }
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
|
|
||||||
updatedMetas to chapters
|
updatedMetas to chapters
|
||||||
}
|
}
|
||||||
|
|
||||||
SetChapterMetasPayload(clientMutationId, updatedMetas, chapters)
|
return SetChapterMetasPayload(clientMutationId, updatedMetas, chapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteChapterMetasItem(
|
data class DeleteChapterMetasItem(
|
||||||
val chapterIds: List<Int>,
|
val chapterIds: List<Int>,
|
||||||
@@ -325,64 +318,63 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteChapterMetas(input: DeleteChapterMetasInput): DataFetcherResult<DeleteChapterMetasPayload?> =
|
fun deleteChapterMetas(input: DeleteChapterMetasInput): DeleteChapterMetasPayload? {
|
||||||
asDataFetcherResult {
|
val (clientMutationId, items) = input
|
||||||
val (clientMutationId, items) = input
|
|
||||||
|
|
||||||
items.forEach { item ->
|
items.forEach { item ->
|
||||||
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
"Either 'keys' or 'prefixes' must be provided for each item"
|
"Either 'keys' or 'prefixes' must be provided for each item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (allDeletedMetas, allChapterIds) =
|
||||||
|
transaction {
|
||||||
|
val deletedMetas = mutableListOf<ChapterMetaType>()
|
||||||
|
val chapterIds = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
val keyCondition: Op<Boolean>? =
|
||||||
|
item.keys?.takeIf { it.isNotEmpty() }?.let { ChapterMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
item.prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (ChapterMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val metaKeyCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val condition = (ChapterMetaTable.ref inList item.chapterIds) and metaKeyCondition
|
||||||
|
|
||||||
|
deletedMetas +=
|
||||||
|
ChapterMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { condition }
|
||||||
|
.map { ChapterMetaType(it) }
|
||||||
|
|
||||||
|
ChapterMetaTable.deleteWhere { condition }
|
||||||
|
chapterIds += item.chapterIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletedMetas to chapterIds
|
||||||
}
|
}
|
||||||
|
|
||||||
val (allDeletedMetas, allChapterIds) =
|
val chapters =
|
||||||
transaction {
|
transaction {
|
||||||
val deletedMetas = mutableListOf<ChapterMetaType>()
|
ChapterTable
|
||||||
val chapterIds = mutableSetOf<Int>()
|
.selectAll()
|
||||||
|
.where { ChapterTable.id inList allChapterIds }
|
||||||
|
.map { ChapterType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
items.forEach { item ->
|
return DeleteChapterMetasPayload(clientMutationId, allDeletedMetas, chapters)
|
||||||
val keyCondition: Op<Boolean>? =
|
}
|
||||||
item.keys?.takeIf { it.isNotEmpty() }?.let { ChapterMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
item.prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (ChapterMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val metaKeyCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val condition = (ChapterMetaTable.ref inList item.chapterIds) and metaKeyCondition
|
|
||||||
|
|
||||||
deletedMetas +=
|
|
||||||
ChapterMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { condition }
|
|
||||||
.map { ChapterMetaType(it) }
|
|
||||||
|
|
||||||
ChapterMetaTable.deleteWhere { condition }
|
|
||||||
chapterIds += item.chapterIds
|
|
||||||
}
|
|
||||||
|
|
||||||
deletedMetas to chapterIds
|
|
||||||
}
|
|
||||||
|
|
||||||
val chapters =
|
|
||||||
transaction {
|
|
||||||
ChapterTable
|
|
||||||
.selectAll()
|
|
||||||
.where { ChapterTable.id inList allChapterIds }
|
|
||||||
.map { ChapterType(it) }
|
|
||||||
.distinctBy { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteChapterMetasPayload(clientMutationId, allDeletedMetas, chapters)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class FetchChapterPagesInput(
|
data class FetchChapterPagesInput(
|
||||||
val clientMutationId: String? = null,
|
val clientMutationId: String? = null,
|
||||||
@@ -405,67 +397,65 @@ class ChapterMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<DataFetcherResult<FetchChapterPagesPayload?>> {
|
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<FetchChapterPagesPayload?> {
|
||||||
val (clientMutationId, chapterId) = input
|
val (clientMutationId, chapterId) = input
|
||||||
val paramsMap = input.toParams()
|
val paramsMap = input.toParams()
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
var chapter = getChapterDownloadReadyById(chapterId)
|
||||||
var chapter = getChapterDownloadReadyById(chapterId)
|
val syncResult = KoreaderSyncService.checkAndPullProgress(chapter.id)
|
||||||
val syncResult = KoreaderSyncService.checkAndPullProgress(chapter.id)
|
var syncConflictInfo: SyncConflictInfoType? = null
|
||||||
var syncConflictInfo: SyncConflictInfoType? = null
|
|
||||||
|
|
||||||
if (syncResult != null) {
|
if (syncResult != null) {
|
||||||
if (syncResult.isConflict) {
|
if (syncResult.isConflict) {
|
||||||
syncConflictInfo =
|
syncConflictInfo =
|
||||||
SyncConflictInfoType(
|
SyncConflictInfoType(
|
||||||
deviceName = syncResult.device,
|
deviceName = syncResult.device,
|
||||||
remotePage = syncResult.pageRead,
|
remotePage = syncResult.pageRead,
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (syncResult.shouldUpdate) {
|
|
||||||
// Update DB for SILENT and RECEIVE
|
|
||||||
transaction {
|
|
||||||
ChapterTable.update({ ChapterTable.id eq chapter.id }) {
|
|
||||||
it[lastPageRead] = syncResult.pageRead
|
|
||||||
it[lastReadAt] = syncResult.timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// For PROMPT, SILENT, and RECEIVE, return the remote progress
|
|
||||||
chapter =
|
|
||||||
chapter.copy(
|
|
||||||
lastPageRead = if (syncResult.shouldUpdate) syncResult.pageRead else chapter.lastPageRead,
|
|
||||||
lastReadAt = if (syncResult.shouldUpdate) syncResult.timestamp else chapter.lastReadAt,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val params =
|
if (syncResult.shouldUpdate) {
|
||||||
buildString {
|
// Update DB for SILENT and RECEIVE
|
||||||
if (paramsMap.isNotEmpty()) {
|
transaction {
|
||||||
append("?")
|
ChapterTable.update({ ChapterTable.id eq chapter.id }) {
|
||||||
paramsMap.entries.forEach { entry ->
|
it[lastPageRead] = syncResult.pageRead
|
||||||
if (length > 1) {
|
it[lastReadAt] = syncResult.timestamp
|
||||||
append("&")
|
|
||||||
}
|
|
||||||
append(entry.key)
|
|
||||||
append("=")
|
|
||||||
append(URLEncoder.encode(entry.value, Charsets.UTF_8))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
FetchChapterPagesPayload(
|
// For PROMPT, SILENT, and RECEIVE, return the remote progress
|
||||||
clientMutationId = clientMutationId,
|
chapter =
|
||||||
pages =
|
chapter.copy(
|
||||||
List(chapter.pageCount) { index ->
|
lastPageRead = if (syncResult.shouldUpdate) syncResult.pageRead else chapter.lastPageRead,
|
||||||
"/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/${index}$params"
|
lastReadAt = if (syncResult.shouldUpdate) syncResult.timestamp else chapter.lastReadAt,
|
||||||
},
|
)
|
||||||
chapter = ChapterType(chapter),
|
|
||||||
syncConflict = syncConflictInfo,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val params =
|
||||||
|
buildString {
|
||||||
|
if (paramsMap.isNotEmpty()) {
|
||||||
|
append("?")
|
||||||
|
paramsMap.entries.forEach { entry ->
|
||||||
|
if (length > 1) {
|
||||||
|
append("&")
|
||||||
|
}
|
||||||
|
append(entry.key)
|
||||||
|
append("=")
|
||||||
|
append(URLEncoder.encode(entry.value, Charsets.UTF_8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchChapterPagesPayload(
|
||||||
|
clientMutationId = clientMutationId,
|
||||||
|
pages =
|
||||||
|
List(chapter.pageCount) { index ->
|
||||||
|
"/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/${index}$params"
|
||||||
|
},
|
||||||
|
chapter = ChapterType(chapter),
|
||||||
|
syncConflict = syncConflictInfo,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
@@ -5,7 +7,6 @@ import kotlinx.coroutines.flow.first
|
|||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||||
import suwayomi.tachidesk.graphql.types.DownloadStatus
|
import suwayomi.tachidesk.graphql.types.DownloadStatus
|
||||||
@@ -30,23 +31,21 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DataFetcherResult<DeleteDownloadedChaptersPayload?> {
|
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DeleteDownloadedChaptersPayload? {
|
||||||
val (clientMutationId, chapters) = input
|
val (clientMutationId, chapters) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Chapter.deleteChapters(chapters)
|
||||||
Chapter.deleteChapters(chapters)
|
|
||||||
|
|
||||||
DeleteDownloadedChaptersPayload(
|
return DeleteDownloadedChaptersPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapters =
|
chapters =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable
|
ChapterTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ChapterTable.id inList chapters }
|
.where { ChapterTable.id inList chapters }
|
||||||
.map { ChapterType(it) }
|
.map { ChapterType(it) }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteDownloadedChapterInput(
|
data class DeleteDownloadedChapterInput(
|
||||||
@@ -60,20 +59,18 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DataFetcherResult<DeleteDownloadedChapterPayload?> {
|
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DeleteDownloadedChapterPayload? {
|
||||||
val (clientMutationId, chapter) = input
|
val (clientMutationId, chapter) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Chapter.deleteChapters(listOf(chapter))
|
||||||
Chapter.deleteChapters(listOf(chapter))
|
|
||||||
|
|
||||||
DeleteDownloadedChapterPayload(
|
return DeleteDownloadedChapterPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
chapters =
|
chapters =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq chapter }.first())
|
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq chapter }.first())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class EnqueueChapterDownloadsInput(
|
data class EnqueueChapterDownloadsInput(
|
||||||
@@ -87,28 +84,24 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun enqueueChapterDownloads(
|
fun enqueueChapterDownloads(input: EnqueueChapterDownloadsInput): CompletableFuture<EnqueueChapterDownloadsPayload?> {
|
||||||
input: EnqueueChapterDownloadsInput,
|
|
||||||
): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadsPayload?>> {
|
|
||||||
val (clientMutationId, chapters) = input
|
val (clientMutationId, chapters) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters))
|
||||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters))
|
|
||||||
|
|
||||||
EnqueueChapterDownloadsPayload(
|
EnqueueChapterDownloadsPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first {
|
.first {
|
||||||
DownloadManager.getStatus().queue.any { it.chapterId in chapters }
|
DownloadManager.getStatus().queue.any { it.chapterId in chapters }
|
||||||
}.let { DownloadManager.getStatus() },
|
}.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,25 +116,23 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadPayload?>> {
|
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<EnqueueChapterDownloadPayload?> {
|
||||||
val (clientMutationId, chapter) = input
|
val (clientMutationId, chapter) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter)))
|
|
||||||
|
|
||||||
EnqueueChapterDownloadPayload(
|
EnqueueChapterDownloadPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.updates.any { it.downloadQueueItem.chapterId == chapter } }
|
.first { it.updates.any { it.downloadQueueItem.chapterId == chapter } }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,30 +147,26 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun dequeueChapterDownloads(
|
fun dequeueChapterDownloads(input: DequeueChapterDownloadsInput): CompletableFuture<DequeueChapterDownloadsPayload?> {
|
||||||
input: DequeueChapterDownloadsInput,
|
|
||||||
): CompletableFuture<DataFetcherResult<DequeueChapterDownloadsPayload?>> {
|
|
||||||
val (clientMutationId, chapters) = input
|
val (clientMutationId, chapters) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters))
|
||||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters))
|
|
||||||
|
|
||||||
DequeueChapterDownloadsPayload(
|
DequeueChapterDownloadsPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first {
|
.first {
|
||||||
it.updates.any {
|
it.updates.any {
|
||||||
it.downloadQueueItem.chapterId in chapters && it.type == DEQUEUED
|
it.downloadQueueItem.chapterId in chapters && it.type == DEQUEUED
|
||||||
}
|
}
|
||||||
}.let { DownloadManager.getStatus() },
|
}.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,28 +181,26 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DataFetcherResult<DequeueChapterDownloadPayload?>> {
|
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DequeueChapterDownloadPayload?> {
|
||||||
val (clientMutationId, chapter) = input
|
val (clientMutationId, chapter) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter)))
|
|
||||||
|
|
||||||
DequeueChapterDownloadPayload(
|
DequeueChapterDownloadPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first {
|
.first {
|
||||||
it.updates.any {
|
it.updates.any {
|
||||||
it.downloadQueueItem.chapterId == chapter && it.type == DEQUEUED
|
it.downloadQueueItem.chapterId == chapter && it.type == DEQUEUED
|
||||||
}
|
}
|
||||||
}.let { DownloadManager.getStatus() },
|
}.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,23 +214,21 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun startDownloader(input: StartDownloaderInput): CompletableFuture<DataFetcherResult<StartDownloaderPayload?>> =
|
fun startDownloader(input: StartDownloaderInput): CompletableFuture<StartDownloaderPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
DownloadManager.start()
|
||||||
DownloadManager.start()
|
|
||||||
|
|
||||||
StartDownloaderPayload(
|
StartDownloaderPayload(
|
||||||
input.clientMutationId,
|
input.clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.status == Status.Started }
|
.first { it.status == Status.Started }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class StopDownloaderInput(
|
data class StopDownloaderInput(
|
||||||
@@ -258,23 +241,21 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<DataFetcherResult<StopDownloaderPayload?>> =
|
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<StopDownloaderPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
DownloadManager.stop()
|
||||||
DownloadManager.stop()
|
|
||||||
|
|
||||||
StopDownloaderPayload(
|
StopDownloaderPayload(
|
||||||
input.clientMutationId,
|
input.clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.status == Status.Stopped }
|
.first { it.status == Status.Stopped }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ClearDownloaderInput(
|
data class ClearDownloaderInput(
|
||||||
@@ -287,23 +268,21 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<DataFetcherResult<ClearDownloaderPayload?>> =
|
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<ClearDownloaderPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
DownloadManager.clear()
|
||||||
DownloadManager.clear()
|
|
||||||
|
|
||||||
ClearDownloaderPayload(
|
ClearDownloaderPayload(
|
||||||
input.clientMutationId,
|
input.clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.status == Status.Stopped }
|
.first { it.status == Status.Stopped }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ReorderChapterDownloadInput(
|
data class ReorderChapterDownloadInput(
|
||||||
@@ -318,25 +297,23 @@ class DownloadMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<DataFetcherResult<ReorderChapterDownloadPayload?>> {
|
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<ReorderChapterDownloadPayload?> {
|
||||||
val (clientMutationId, chapter, to) = input
|
val (clientMutationId, chapter, to) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
DownloadManager.reorder(chapter, to)
|
||||||
DownloadManager.reorder(chapter, to)
|
|
||||||
|
|
||||||
ReorderChapterDownloadPayload(
|
ReorderChapterDownloadPayload(
|
||||||
clientMutationId,
|
clientMutationId,
|
||||||
downloadStatus =
|
downloadStatus =
|
||||||
withTimeout(30.seconds) {
|
withTimeout(30.seconds) {
|
||||||
DownloadStatus(
|
DownloadStatus(
|
||||||
DownloadManager.updates
|
DownloadManager.updates
|
||||||
.first { it.updates.indexOfFirst { it.downloadQueueItem.chapterId == chapter } <= to }
|
.first { it.updates.indexOfFirst { it.downloadQueueItem.chapterId == chapter } <= to }
|
||||||
.let { DownloadManager.getStatus() },
|
.let { DownloadManager.getStatus() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||||
@@ -5,7 +7,6 @@ import graphql.execution.DataFetcherResult
|
|||||||
import io.javalin.http.UploadedFile
|
import io.javalin.http.UploadedFile
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.ExtensionType
|
import suwayomi.tachidesk.graphql.types.ExtensionType
|
||||||
import suwayomi.tachidesk.manga.impl.extension.Extension
|
import suwayomi.tachidesk.manga.impl.extension.Extension
|
||||||
@@ -75,51 +76,47 @@ class ExtensionMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateExtension(input: UpdateExtensionInput): CompletableFuture<DataFetcherResult<UpdateExtensionPayload?>> {
|
fun updateExtension(input: UpdateExtensionInput): CompletableFuture<UpdateExtensionPayload?> {
|
||||||
val (clientMutationId, id, patch) = input
|
val (clientMutationId, id, patch) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
updateExtensions(listOf(id), patch)
|
||||||
updateExtensions(listOf(id), patch)
|
|
||||||
|
|
||||||
val extension =
|
val extension =
|
||||||
transaction {
|
transaction {
|
||||||
ExtensionTable
|
ExtensionTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ExtensionTable.pkgName eq id }
|
.where { ExtensionTable.pkgName eq id }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { ExtensionType(it) }
|
?.let { ExtensionType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateExtensionPayload(
|
UpdateExtensionPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
extension = extension,
|
extension = extension,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateExtensions(input: UpdateExtensionsInput): CompletableFuture<DataFetcherResult<UpdateExtensionsPayload?>> {
|
fun updateExtensions(input: UpdateExtensionsInput): CompletableFuture<UpdateExtensionsPayload?> {
|
||||||
val (clientMutationId, ids, patch) = input
|
val (clientMutationId, ids, patch) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
updateExtensions(ids, patch)
|
||||||
updateExtensions(ids, patch)
|
|
||||||
|
|
||||||
val extensions =
|
val extensions =
|
||||||
transaction {
|
transaction {
|
||||||
ExtensionTable
|
ExtensionTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ExtensionTable.pkgName inList ids }
|
.where { ExtensionTable.pkgName inList ids }
|
||||||
.map { ExtensionType(it) }
|
.map { ExtensionType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateExtensionsPayload(
|
UpdateExtensionsPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
extensions = extensions,
|
extensions = extensions,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,26 +130,24 @@ class ExtensionMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<DataFetcherResult<FetchExtensionsPayload?>> {
|
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<FetchExtensionsPayload?> {
|
||||||
val (clientMutationId) = input
|
val (clientMutationId) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
ExtensionsList.fetchExtensions()
|
||||||
ExtensionsList.fetchExtensions()
|
|
||||||
|
|
||||||
val extensions =
|
val extensions =
|
||||||
transaction {
|
transaction {
|
||||||
ExtensionTable
|
ExtensionTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
.where { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
||||||
.map { ExtensionType(it) }
|
.map { ExtensionType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchExtensionsPayload(
|
FetchExtensionsPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
extensions = extensions,
|
extensions = extensions,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,23 +162,19 @@ class ExtensionMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun installExternalExtension(
|
fun installExternalExtension(input: InstallExternalExtensionInput): CompletableFuture<InstallExternalExtensionPayload?> {
|
||||||
input: InstallExternalExtensionInput,
|
|
||||||
): CompletableFuture<DataFetcherResult<InstallExternalExtensionPayload?>> {
|
|
||||||
val (clientMutationId, extensionFile) = input
|
val (clientMutationId, extensionFile) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
Extension.installExternalExtension(extensionFile.content(), extensionFile.filename())
|
||||||
Extension.installExternalExtension(extensionFile.content(), extensionFile.filename())
|
|
||||||
|
|
||||||
val dbExtension =
|
val dbExtension =
|
||||||
transaction { ExtensionTable.selectAll().where { ExtensionTable.apkName eq extensionFile.filename() }.first() }
|
transaction { ExtensionTable.selectAll().where { ExtensionTable.apkName eq extensionFile.filename() }.first() }
|
||||||
|
|
||||||
InstallExternalExtensionPayload(
|
InstallExternalExtensionPayload(
|
||||||
clientMutationId,
|
clientMutationId,
|
||||||
extension = ExtensionType(dbExtension),
|
extension = ExtensionType(dbExtension),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateState.ERROR
|
import suwayomi.tachidesk.graphql.types.UpdateState.ERROR
|
||||||
@@ -26,55 +27,51 @@ class InfoMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<DataFetcherResult<WebUIUpdatePayload?>> {
|
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<WebUIUpdatePayload?> {
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
if (WebInterfaceManager.status.value.state === DOWNLOADING) {
|
||||||
if (WebInterfaceManager.status.value.state === DOWNLOADING) {
|
return@withTimeout WebUIUpdatePayload(input.clientMutationId, WebInterfaceManager.status.value)
|
||||||
return@withTimeout WebUIUpdatePayload(input.clientMutationId, WebInterfaceManager.status.value)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val flavor = WebUIFlavor.current
|
val flavor = WebUIFlavor.current
|
||||||
|
|
||||||
val (version, updateAvailable) = WebInterfaceManager.isUpdateAvailable(flavor)
|
val (version, updateAvailable) = WebInterfaceManager.isUpdateAvailable(flavor)
|
||||||
|
|
||||||
if (!updateAvailable) {
|
if (!updateAvailable) {
|
||||||
val didUpdateCheckFail = version.isEmpty()
|
val didUpdateCheckFail = version.isEmpty()
|
||||||
|
|
||||||
return@withTimeout WebUIUpdatePayload(
|
return@withTimeout WebUIUpdatePayload(
|
||||||
input.clientMutationId,
|
|
||||||
WebInterfaceManager.getStatus(version, if (didUpdateCheckFail) ERROR else IDLE),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
WebInterfaceManager.startDownloadInScope(flavor, version)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// ignore since we use the status anyway
|
|
||||||
}
|
|
||||||
|
|
||||||
WebUIUpdatePayload(
|
|
||||||
input.clientMutationId,
|
input.clientMutationId,
|
||||||
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING },
|
WebInterfaceManager.getStatus(version, if (didUpdateCheckFail) ERROR else IDLE),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
WebInterfaceManager.startDownloadInScope(flavor, version)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// ignore since we use the status anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
WebUIUpdatePayload(
|
||||||
|
input.clientMutationId,
|
||||||
|
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun resetWebUIUpdateStatus(): CompletableFuture<DataFetcherResult<WebUIUpdateStatus?>> =
|
fun resetWebUIUpdateStatus(): CompletableFuture<WebUIUpdateStatus?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
val isUpdateFinished = WebInterfaceManager.status.value.state != DOWNLOADING
|
||||||
val isUpdateFinished = WebInterfaceManager.status.value.state != DOWNLOADING
|
if (!isUpdateFinished) {
|
||||||
if (!isUpdateFinished) {
|
throw Exception("Status reset is not allowed during status \"$DOWNLOADING\"")
|
||||||
throw Exception("Status reset is not allowed during status \"$DOWNLOADING\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
WebInterfaceManager.resetStatus()
|
|
||||||
|
|
||||||
WebInterfaceManager.status.first { it.state == IDLE }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebInterfaceManager.resetStatus()
|
||||||
|
|
||||||
|
WebInterfaceManager.status.first { it.state == IDLE }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||||
import suwayomi.tachidesk.graphql.types.KoSyncConnectPayload
|
import suwayomi.tachidesk.graphql.types.KoSyncConnectPayload
|
||||||
@@ -62,26 +63,24 @@ class KoreaderSyncMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun pushKoSyncProgress(input: PushKoSyncProgressInput): CompletableFuture<DataFetcherResult<PushKoSyncProgressPayload?>> =
|
fun pushKoSyncProgress(input: PushKoSyncProgressInput): CompletableFuture<PushKoSyncProgressPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
KoreaderSyncService.pushProgress(input.chapterId)
|
||||||
KoreaderSyncService.pushProgress(input.chapterId)
|
|
||||||
|
|
||||||
val chapter =
|
val chapter =
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable
|
ChapterTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { ChapterTable.id eq input.chapterId }
|
.where { ChapterTable.id eq input.chapterId }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { ChapterType(it) }
|
?.let { ChapterType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
PushKoSyncProgressPayload(
|
PushKoSyncProgressPayload(
|
||||||
clientMutationId = input.clientMutationId,
|
clientMutationId = input.clientMutationId,
|
||||||
success = true,
|
success = true,
|
||||||
chapter = chapter,
|
chapter = chapter,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class PullKoSyncProgressInput(
|
data class PullKoSyncProgressInput(
|
||||||
@@ -96,45 +95,43 @@ class KoreaderSyncMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun pullKoSyncProgress(input: PullKoSyncProgressInput): CompletableFuture<DataFetcherResult<PullKoSyncProgressPayload?>> =
|
fun pullKoSyncProgress(input: PullKoSyncProgressInput): CompletableFuture<PullKoSyncProgressPayload?> =
|
||||||
future {
|
future {
|
||||||
asDataFetcherResult {
|
val syncResult = KoreaderSyncService.checkAndPullProgress(input.chapterId)
|
||||||
val syncResult = KoreaderSyncService.checkAndPullProgress(input.chapterId)
|
var syncConflictInfo: SyncConflictInfoType? = null
|
||||||
var syncConflictInfo: SyncConflictInfoType? = null
|
|
||||||
|
|
||||||
if (syncResult != null) {
|
if (syncResult != null) {
|
||||||
if (syncResult.isConflict) {
|
if (syncResult.isConflict) {
|
||||||
syncConflictInfo =
|
syncConflictInfo =
|
||||||
SyncConflictInfoType(
|
SyncConflictInfoType(
|
||||||
deviceName = syncResult.device,
|
deviceName = syncResult.device,
|
||||||
remotePage = syncResult.pageRead,
|
remotePage = syncResult.pageRead,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncResult.shouldUpdate) {
|
if (syncResult.shouldUpdate) {
|
||||||
transaction {
|
transaction {
|
||||||
ChapterTable.update({ ChapterTable.id eq input.chapterId }) {
|
ChapterTable.update({ ChapterTable.id eq input.chapterId }) {
|
||||||
it[lastPageRead] = syncResult.pageRead
|
it[lastPageRead] = syncResult.pageRead
|
||||||
it[lastReadAt] = syncResult.timestamp
|
it[lastReadAt] = syncResult.timestamp
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val chapter =
|
|
||||||
transaction {
|
|
||||||
ChapterTable
|
|
||||||
.selectAll()
|
|
||||||
.where { ChapterTable.id eq input.chapterId }
|
|
||||||
.firstOrNull()
|
|
||||||
?.let { ChapterType(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
PullKoSyncProgressPayload(
|
|
||||||
clientMutationId = input.clientMutationId,
|
|
||||||
chapter = chapter,
|
|
||||||
syncConflict = syncConflictInfo,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val chapter =
|
||||||
|
transaction {
|
||||||
|
ChapterTable
|
||||||
|
.selectAll()
|
||||||
|
.where { ChapterTable.id eq input.chapterId }
|
||||||
|
.firstOrNull()
|
||||||
|
?.let { ChapterType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
PullKoSyncProgressPayload(
|
||||||
|
clientMutationId = input.clientMutationId,
|
||||||
|
chapter = chapter,
|
||||||
|
syncConflict = syncConflictInfo,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
|
||||||
import org.jetbrains.exposed.sql.LikePattern
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
import org.jetbrains.exposed.sql.Op
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
@@ -12,7 +13,6 @@ import org.jetbrains.exposed.sql.or
|
|||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.MangaMetaType
|
import suwayomi.tachidesk.graphql.types.MangaMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.MangaType
|
import suwayomi.tachidesk.graphql.types.MangaType
|
||||||
@@ -98,44 +98,40 @@ class MangaMutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateManga(input: UpdateMangaInput): CompletableFuture<DataFetcherResult<UpdateMangaPayload?>> {
|
fun updateManga(input: UpdateMangaInput): CompletableFuture<UpdateMangaPayload?> {
|
||||||
val (clientMutationId, id, patch) = input
|
val (clientMutationId, id, patch) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
updateMangas(listOf(id), patch)
|
||||||
updateMangas(listOf(id), patch)
|
|
||||||
|
|
||||||
val manga =
|
val manga =
|
||||||
transaction {
|
transaction {
|
||||||
MangaType(MangaTable.selectAll().where { MangaTable.id eq id }.first())
|
MangaType(MangaTable.selectAll().where { MangaTable.id eq id }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMangaPayload(
|
UpdateMangaPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
manga = manga,
|
manga = manga,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateMangas(input: UpdateMangasInput): CompletableFuture<DataFetcherResult<UpdateMangasPayload?>> {
|
fun updateMangas(input: UpdateMangasInput): CompletableFuture<UpdateMangasPayload?> {
|
||||||
val (clientMutationId, ids, patch) = input
|
val (clientMutationId, ids, patch) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
updateMangas(ids, patch)
|
||||||
updateMangas(ids, patch)
|
|
||||||
|
|
||||||
val mangas =
|
val mangas =
|
||||||
transaction {
|
transaction {
|
||||||
MangaTable.selectAll().where { MangaTable.id inList ids }.map { MangaType(it) }
|
MangaTable.selectAll().where { MangaTable.id inList ids }.map { MangaType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMangasPayload(
|
UpdateMangasPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
mangas = mangas,
|
mangas = mangas,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,22 +146,20 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchManga(input: FetchMangaInput): CompletableFuture<DataFetcherResult<FetchMangaPayload?>> {
|
fun fetchManga(input: FetchMangaInput): CompletableFuture<FetchMangaPayload?> {
|
||||||
val (clientMutationId, id) = input
|
val (clientMutationId, id) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
Manga.fetchManga(id)
|
||||||
Manga.fetchManga(id)
|
|
||||||
|
|
||||||
val manga =
|
val manga =
|
||||||
transaction {
|
transaction {
|
||||||
MangaTable.selectAll().where { MangaTable.id eq id }.first()
|
MangaTable.selectAll().where { MangaTable.id eq id }.first()
|
||||||
}
|
}
|
||||||
FetchMangaPayload(
|
FetchMangaPayload(
|
||||||
clientMutationId = clientMutationId,
|
clientMutationId = clientMutationId,
|
||||||
manga = MangaType(manga),
|
manga = MangaType(manga),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,14 +174,12 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setMangaMeta(input: SetMangaMetaInput): DataFetcherResult<SetMangaMetaPayload?> {
|
fun setMangaMeta(input: SetMangaMetaInput): SetMangaMetaPayload? {
|
||||||
val (clientMutationId, meta) = input
|
val (clientMutationId, meta) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value)
|
||||||
Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value)
|
|
||||||
|
|
||||||
SetMangaMetaPayload(clientMutationId, meta)
|
return SetMangaMetaPayload(clientMutationId, meta)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteMangaMetaInput(
|
data class DeleteMangaMetaInput(
|
||||||
@@ -203,34 +195,32 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteMangaMeta(input: DeleteMangaMetaInput): DataFetcherResult<DeleteMangaMetaPayload?> {
|
fun deleteMangaMeta(input: DeleteMangaMetaInput): DeleteMangaMetaPayload? {
|
||||||
val (clientMutationId, mangaId, key) = input
|
val (clientMutationId, mangaId, key) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val (meta, manga) =
|
||||||
val (meta, manga) =
|
transaction {
|
||||||
transaction {
|
val meta =
|
||||||
val meta =
|
MangaMetaTable
|
||||||
MangaMetaTable
|
.selectAll()
|
||||||
.selectAll()
|
.where { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||||
.where { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
.firstOrNull()
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||||
|
|
||||||
val manga =
|
val manga =
|
||||||
transaction {
|
transaction {
|
||||||
MangaType(MangaTable.selectAll().where { MangaTable.id eq mangaId }.first())
|
MangaType(MangaTable.selectAll().where { MangaTable.id eq mangaId }.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta != null) {
|
if (meta != null) {
|
||||||
MangaMetaType(meta)
|
MangaMetaType(meta)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
} to manga
|
} to manga
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteMangaMetaPayload(clientMutationId, meta, manga)
|
return DeleteMangaMetaPayload(clientMutationId, meta, manga)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetMangaMetasItem(
|
data class SetMangaMetasItem(
|
||||||
@@ -250,43 +240,41 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setMangaMetas(input: SetMangaMetasInput): DataFetcherResult<SetMangaMetasPayload?> {
|
fun setMangaMetas(input: SetMangaMetasInput): SetMangaMetasPayload? {
|
||||||
val (clientMutationId, items) = input
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val metaByMangaId =
|
||||||
val metaByMangaId =
|
items
|
||||||
items
|
.flatMap { item ->
|
||||||
.flatMap { item ->
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
val metaMap = item.metas.associate { it.key to it.value }
|
item.mangaIds.map { mangaId -> mangaId to metaMap }
|
||||||
item.mangaIds.map { mangaId -> mangaId to metaMap }
|
}.groupBy({ it.first }, { it.second })
|
||||||
}.groupBy({ it.first }, { it.second })
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
|
||||||
|
|
||||||
Manga.modifyMangasMetas(metaByMangaId)
|
Manga.modifyMangasMetas(metaByMangaId)
|
||||||
|
|
||||||
val allMangaIds = metaByMangaId.keys
|
val allMangaIds = metaByMangaId.keys
|
||||||
val allMetaKeys = metaByMangaId.values.flatMap { it.keys }.distinct()
|
val allMetaKeys = metaByMangaId.values.flatMap { it.keys }.distinct()
|
||||||
|
|
||||||
val (updatedMetas, mangas) =
|
val (updatedMetas, mangas) =
|
||||||
transaction {
|
transaction {
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
MangaMetaTable
|
MangaMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (MangaMetaTable.ref inList allMangaIds) and (MangaMetaTable.key inList allMetaKeys) }
|
.where { (MangaMetaTable.ref inList allMangaIds) and (MangaMetaTable.key inList allMetaKeys) }
|
||||||
.map { MangaMetaType(it) }
|
.map { MangaMetaType(it) }
|
||||||
|
|
||||||
val mangas =
|
val mangas =
|
||||||
MangaTable
|
MangaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { MangaTable.id inList allMangaIds }
|
.where { MangaTable.id inList allMangaIds }
|
||||||
.map { MangaType(it) }
|
.map { MangaType(it) }
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
|
|
||||||
updatedMetas to mangas
|
updatedMetas to mangas
|
||||||
}
|
}
|
||||||
|
|
||||||
SetMangaMetasPayload(clientMutationId, updatedMetas, mangas)
|
return SetMangaMetasPayload(clientMutationId, updatedMetas, mangas)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteMangaMetasItem(
|
data class DeleteMangaMetasItem(
|
||||||
@@ -307,63 +295,61 @@ class MangaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteMangaMetas(input: DeleteMangaMetasInput): DataFetcherResult<DeleteMangaMetasPayload?> {
|
fun deleteMangaMetas(input: DeleteMangaMetasInput): DeleteMangaMetasPayload? {
|
||||||
val (clientMutationId, items) = input
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
items.forEach { item ->
|
||||||
items.forEach { item ->
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
"Either 'keys' or 'prefixes' must be provided for each item"
|
||||||
"Either 'keys' or 'prefixes' must be provided for each item"
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (allDeletedMetas, allMangaIds) =
|
||||||
|
transaction {
|
||||||
|
val deletedMetas = mutableListOf<MangaMetaType>()
|
||||||
|
val mangaIds = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
val keyCondition: Op<Boolean>? =
|
||||||
|
item.keys?.takeIf { it.isNotEmpty() }?.let { MangaMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
item.prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (MangaMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val metaKeyCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val condition = (MangaMetaTable.ref inList item.mangaIds) and metaKeyCondition
|
||||||
|
|
||||||
|
deletedMetas +=
|
||||||
|
MangaMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { condition }
|
||||||
|
.map { MangaMetaType(it) }
|
||||||
|
|
||||||
|
MangaMetaTable.deleteWhere { condition }
|
||||||
|
mangaIds += item.mangaIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletedMetas to mangaIds
|
||||||
}
|
}
|
||||||
|
|
||||||
val (allDeletedMetas, allMangaIds) =
|
val mangas =
|
||||||
transaction {
|
transaction {
|
||||||
val deletedMetas = mutableListOf<MangaMetaType>()
|
MangaTable
|
||||||
val mangaIds = mutableSetOf<Int>()
|
.selectAll()
|
||||||
|
.where { MangaTable.id inList allMangaIds }
|
||||||
|
.map { MangaType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
items.forEach { item ->
|
return DeleteMangaMetasPayload(clientMutationId, allDeletedMetas, mangas)
|
||||||
val keyCondition: Op<Boolean>? =
|
|
||||||
item.keys?.takeIf { it.isNotEmpty() }?.let { MangaMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
item.prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (MangaMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val metaKeyCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val condition = (MangaMetaTable.ref inList item.mangaIds) and metaKeyCondition
|
|
||||||
|
|
||||||
deletedMetas +=
|
|
||||||
MangaMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { condition }
|
|
||||||
.map { MangaMetaType(it) }
|
|
||||||
|
|
||||||
MangaMetaTable.deleteWhere { condition }
|
|
||||||
mangaIds += item.mangaIds
|
|
||||||
}
|
|
||||||
|
|
||||||
deletedMetas to mangaIds
|
|
||||||
}
|
|
||||||
|
|
||||||
val mangas =
|
|
||||||
transaction {
|
|
||||||
MangaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { MangaTable.id inList allMangaIds }
|
|
||||||
.map { MangaType(it) }
|
|
||||||
.distinctBy { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteMangaMetasPayload(clientMutationId, allDeletedMetas, mangas)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
@@ -12,7 +14,6 @@ import org.jetbrains.exposed.sql.selectAll
|
|||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.global.impl.GlobalMeta
|
import suwayomi.tachidesk.global.impl.GlobalMeta
|
||||||
import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.GlobalMetaType
|
import suwayomi.tachidesk.graphql.types.GlobalMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.MetaInput
|
import suwayomi.tachidesk.graphql.types.MetaInput
|
||||||
@@ -29,14 +30,12 @@ class MetaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setGlobalMeta(input: SetGlobalMetaInput): DataFetcherResult<SetGlobalMetaPayload?> {
|
fun setGlobalMeta(input: SetGlobalMetaInput): SetGlobalMetaPayload? {
|
||||||
val (clientMutationId, meta) = input
|
val (clientMutationId, meta) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
GlobalMeta.modifyMeta(meta.key, meta.value)
|
||||||
GlobalMeta.modifyMeta(meta.key, meta.value)
|
|
||||||
|
|
||||||
SetGlobalMetaPayload(clientMutationId, meta)
|
return SetGlobalMetaPayload(clientMutationId, meta)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteGlobalMetaInput(
|
data class DeleteGlobalMetaInput(
|
||||||
@@ -50,29 +49,27 @@ class MetaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DataFetcherResult<DeleteGlobalMetaPayload?> {
|
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DeleteGlobalMetaPayload? {
|
||||||
val (clientMutationId, key) = input
|
val (clientMutationId, key) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val meta =
|
||||||
val meta =
|
transaction {
|
||||||
transaction {
|
val meta =
|
||||||
val meta =
|
GlobalMetaTable
|
||||||
GlobalMetaTable
|
.selectAll()
|
||||||
.selectAll()
|
.where { GlobalMetaTable.key eq key }
|
||||||
.where { GlobalMetaTable.key eq key }
|
.firstOrNull()
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key }
|
GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key }
|
||||||
|
|
||||||
if (meta != null) {
|
if (meta != null) {
|
||||||
GlobalMetaType(meta)
|
GlobalMetaType(meta)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DeleteGlobalMetaPayload(clientMutationId, meta)
|
return DeleteGlobalMetaPayload(clientMutationId, meta)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetGlobalMetasInput(
|
data class SetGlobalMetasInput(
|
||||||
@@ -86,23 +83,21 @@ class MetaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setGlobalMetas(input: SetGlobalMetasInput): DataFetcherResult<SetGlobalMetasPayload?> {
|
fun setGlobalMetas(input: SetGlobalMetasInput): SetGlobalMetasPayload? {
|
||||||
val (clientMutationId, metas) = input
|
val (clientMutationId, metas) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val metaMap = metas.associate { it.key to it.value }
|
||||||
val metaMap = metas.associate { it.key to it.value }
|
GlobalMeta.modifyMetas(metaMap)
|
||||||
GlobalMeta.modifyMetas(metaMap)
|
|
||||||
|
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
transaction {
|
transaction {
|
||||||
GlobalMetaTable
|
GlobalMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { GlobalMetaTable.key inList metaMap.keys }
|
.where { GlobalMetaTable.key inList metaMap.keys }
|
||||||
.map { GlobalMetaType(it) }
|
.map { GlobalMetaType(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
SetGlobalMetasPayload(clientMutationId, updatedMetas)
|
return SetGlobalMetasPayload(clientMutationId, updatedMetas)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteGlobalMetasInput(
|
data class DeleteGlobalMetasInput(
|
||||||
@@ -117,43 +112,41 @@ class MetaMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteGlobalMetas(input: DeleteGlobalMetasInput): DataFetcherResult<DeleteGlobalMetasPayload?> {
|
fun deleteGlobalMetas(input: DeleteGlobalMetasInput): DeleteGlobalMetasPayload? {
|
||||||
val (clientMutationId, keys, prefixes) = input
|
val (clientMutationId, keys, prefixes) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
require(!keys.isNullOrEmpty() || !prefixes.isNullOrEmpty()) {
|
||||||
require(!keys.isNullOrEmpty() || !prefixes.isNullOrEmpty()) {
|
"Either 'keys' or 'prefixes' must be provided"
|
||||||
"Either 'keys' or 'prefixes' must be provided"
|
}
|
||||||
|
|
||||||
|
val metas =
|
||||||
|
transaction {
|
||||||
|
val keyCondition: Op<Boolean>? = keys?.takeIf { it.isNotEmpty() }?.let { GlobalMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (GlobalMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val finalCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val metas =
|
||||||
|
GlobalMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { finalCondition }
|
||||||
|
.map { GlobalMetaType(it) }
|
||||||
|
|
||||||
|
GlobalMetaTable.deleteWhere { finalCondition }
|
||||||
|
|
||||||
|
metas
|
||||||
}
|
}
|
||||||
|
|
||||||
val metas =
|
return DeleteGlobalMetasPayload(clientMutationId, metas)
|
||||||
transaction {
|
|
||||||
val keyCondition: Op<Boolean>? = keys?.takeIf { it.isNotEmpty() }?.let { GlobalMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (GlobalMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val finalCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val metas =
|
|
||||||
GlobalMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { finalCondition }
|
|
||||||
.map { GlobalMetaType(it) }
|
|
||||||
|
|
||||||
GlobalMetaTable.deleteWhere { finalCondition }
|
|
||||||
|
|
||||||
metas
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteGlobalMetasPayload(clientMutationId, metas)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
|
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import androidx.preference.CheckBoxPreference
|
import androidx.preference.CheckBoxPreference
|
||||||
@@ -5,7 +7,6 @@ import androidx.preference.EditTextPreference
|
|||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.MultiSelectListPreference
|
import androidx.preference.MultiSelectListPreference
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import graphql.execution.DataFetcherResult
|
|
||||||
import org.jetbrains.exposed.sql.LikePattern
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
import org.jetbrains.exposed.sql.Op
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
@@ -16,7 +17,6 @@ import org.jetbrains.exposed.sql.deleteWhere
|
|||||||
import org.jetbrains.exposed.sql.or
|
import org.jetbrains.exposed.sql.or
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.FilterChange
|
import suwayomi.tachidesk.graphql.types.FilterChange
|
||||||
import suwayomi.tachidesk.graphql.types.MangaType
|
import suwayomi.tachidesk.graphql.types.MangaType
|
||||||
@@ -47,14 +47,12 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setSourceMeta(input: SetSourceMetaInput): DataFetcherResult<SetSourceMetaPayload?> {
|
fun setSourceMeta(input: SetSourceMetaInput): SetSourceMetaPayload? {
|
||||||
val (clientMutationId, meta) = input
|
val (clientMutationId, meta) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Source.modifyMeta(meta.sourceId, meta.key, meta.value)
|
||||||
Source.modifyMeta(meta.sourceId, meta.key, meta.value)
|
|
||||||
|
|
||||||
SetSourceMetaPayload(clientMutationId, meta)
|
return SetSourceMetaPayload(clientMutationId, meta)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteSourceMetaInput(
|
data class DeleteSourceMetaInput(
|
||||||
@@ -70,38 +68,36 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteSourceMeta(input: DeleteSourceMetaInput): DataFetcherResult<DeleteSourceMetaPayload?> {
|
fun deleteSourceMeta(input: DeleteSourceMetaInput): DeleteSourceMetaPayload? {
|
||||||
val (clientMutationId, sourceId, key) = input
|
val (clientMutationId, sourceId, key) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val (meta, source) =
|
||||||
val (meta, source) =
|
transaction {
|
||||||
transaction {
|
val meta =
|
||||||
val meta =
|
SourceMetaTable
|
||||||
SourceMetaTable
|
.selectAll()
|
||||||
|
.where { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
SourceMetaTable.deleteWhere { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
||||||
|
|
||||||
|
val source =
|
||||||
|
transaction {
|
||||||
|
SourceTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
.where { SourceTable.id eq sourceId }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
?.let { SourceType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
SourceMetaTable.deleteWhere { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
if (meta != null) {
|
||||||
|
SourceMetaType(meta)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
} to source
|
||||||
|
}
|
||||||
|
|
||||||
val source =
|
return DeleteSourceMetaPayload(clientMutationId, meta, source)
|
||||||
transaction {
|
|
||||||
SourceTable
|
|
||||||
.selectAll()
|
|
||||||
.where { SourceTable.id eq sourceId }
|
|
||||||
.firstOrNull()
|
|
||||||
?.let { SourceType(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meta != null) {
|
|
||||||
SourceMetaType(meta)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
} to source
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteSourceMetaPayload(clientMutationId, meta, source)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SetSourceMetasItem(
|
data class SetSourceMetasItem(
|
||||||
@@ -121,43 +117,41 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun setSourceMetas(input: SetSourceMetasInput): DataFetcherResult<SetSourceMetasPayload?> {
|
fun setSourceMetas(input: SetSourceMetasInput): SetSourceMetasPayload? {
|
||||||
val (clientMutationId, items) = input
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
val metaBySourceId =
|
||||||
val metaBySourceId =
|
items
|
||||||
items
|
.flatMap { item ->
|
||||||
.flatMap { item ->
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
val metaMap = item.metas.associate { it.key to it.value }
|
item.sourceIds.map { sourceId -> sourceId to metaMap }
|
||||||
item.sourceIds.map { sourceId -> sourceId to metaMap }
|
}.groupBy({ it.first }, { it.second })
|
||||||
}.groupBy({ it.first }, { it.second })
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
|
||||||
|
|
||||||
Source.modifySourceMetas(metaBySourceId)
|
Source.modifySourceMetas(metaBySourceId)
|
||||||
|
|
||||||
val allSourceIds = metaBySourceId.keys
|
val allSourceIds = metaBySourceId.keys
|
||||||
val allMetaKeys = metaBySourceId.values.flatMap { it.keys }.distinct()
|
val allMetaKeys = metaBySourceId.values.flatMap { it.keys }.distinct()
|
||||||
|
|
||||||
val (updatedMetas, sources) =
|
val (updatedMetas, sources) =
|
||||||
transaction {
|
transaction {
|
||||||
val updatedMetas =
|
val updatedMetas =
|
||||||
SourceMetaTable
|
SourceMetaTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { (SourceMetaTable.ref inList allSourceIds) and (SourceMetaTable.key inList allMetaKeys) }
|
.where { (SourceMetaTable.ref inList allSourceIds) and (SourceMetaTable.key inList allMetaKeys) }
|
||||||
.map { SourceMetaType(it) }
|
.map { SourceMetaType(it) }
|
||||||
|
|
||||||
val sources =
|
val sources =
|
||||||
SourceTable
|
SourceTable
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where { SourceTable.id inList allSourceIds }
|
.where { SourceTable.id inList allSourceIds }
|
||||||
.mapNotNull { SourceType(it) }
|
.mapNotNull { SourceType(it) }
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
|
|
||||||
updatedMetas to sources
|
updatedMetas to sources
|
||||||
}
|
}
|
||||||
|
|
||||||
SetSourceMetasPayload(clientMutationId, updatedMetas, sources)
|
return SetSourceMetasPayload(clientMutationId, updatedMetas, sources)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DeleteSourceMetasItem(
|
data class DeleteSourceMetasItem(
|
||||||
@@ -178,64 +172,62 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun deleteSourceMetas(input: DeleteSourceMetasInput): DataFetcherResult<DeleteSourceMetasPayload?> {
|
fun deleteSourceMetas(input: DeleteSourceMetasInput): DeleteSourceMetasPayload? {
|
||||||
val (clientMutationId, items) = input
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
items.forEach { item ->
|
||||||
items.forEach { item ->
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
"Either 'keys' or 'prefixes' must be provided for each item"
|
||||||
"Either 'keys' or 'prefixes' must be provided for each item"
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (allDeletedMetas, allSourceIds) =
|
||||||
|
transaction {
|
||||||
|
val deletedMetas = mutableListOf<SourceMetaType>()
|
||||||
|
val sourceIds = mutableSetOf<Long>()
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
val keyCondition: Op<Boolean>? =
|
||||||
|
item.keys?.takeIf { it.isNotEmpty() }?.let { SourceMetaTable.key inList it }
|
||||||
|
|
||||||
|
val prefixCondition: Op<Boolean>? =
|
||||||
|
item.prefixes
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?.map { (SourceMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
||||||
|
?.reduceOrNull { acc, op -> acc or op }
|
||||||
|
|
||||||
|
val metaKeyCondition =
|
||||||
|
if (keyCondition != null && prefixCondition != null) {
|
||||||
|
keyCondition or prefixCondition
|
||||||
|
} else {
|
||||||
|
keyCondition ?: prefixCondition!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val condition = (SourceMetaTable.ref inList item.sourceIds) and metaKeyCondition
|
||||||
|
|
||||||
|
deletedMetas +=
|
||||||
|
SourceMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { condition }
|
||||||
|
.map { SourceMetaType(it) }
|
||||||
|
|
||||||
|
SourceMetaTable.deleteWhere { condition }
|
||||||
|
sourceIds += item.sourceIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletedMetas to sourceIds
|
||||||
}
|
}
|
||||||
|
|
||||||
val (allDeletedMetas, allSourceIds) =
|
val sources =
|
||||||
transaction {
|
transaction {
|
||||||
val deletedMetas = mutableListOf<SourceMetaType>()
|
SourceTable
|
||||||
val sourceIds = mutableSetOf<Long>()
|
.selectAll()
|
||||||
|
.where { SourceTable.id inList allSourceIds }
|
||||||
|
.mapNotNull { SourceType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
items.forEach { item ->
|
return DeleteSourceMetasPayload(clientMutationId, allDeletedMetas, sources)
|
||||||
val keyCondition: Op<Boolean>? =
|
|
||||||
item.keys?.takeIf { it.isNotEmpty() }?.let { SourceMetaTable.key inList it }
|
|
||||||
|
|
||||||
val prefixCondition: Op<Boolean>? =
|
|
||||||
item.prefixes
|
|
||||||
?.filter { it.isNotEmpty() }
|
|
||||||
?.map { (SourceMetaTable.key like LikePattern("$it%")) as Op<Boolean> }
|
|
||||||
?.reduceOrNull { acc, op -> acc or op }
|
|
||||||
|
|
||||||
val metaKeyCondition =
|
|
||||||
if (keyCondition != null && prefixCondition != null) {
|
|
||||||
keyCondition or prefixCondition
|
|
||||||
} else {
|
|
||||||
keyCondition ?: prefixCondition!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val condition = (SourceMetaTable.ref inList item.sourceIds) and metaKeyCondition
|
|
||||||
|
|
||||||
deletedMetas +=
|
|
||||||
SourceMetaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { condition }
|
|
||||||
.map { SourceMetaType(it) }
|
|
||||||
|
|
||||||
SourceMetaTable.deleteWhere { condition }
|
|
||||||
sourceIds += item.sourceIds
|
|
||||||
}
|
|
||||||
|
|
||||||
deletedMetas to sourceIds
|
|
||||||
}
|
|
||||||
|
|
||||||
val sources =
|
|
||||||
transaction {
|
|
||||||
SourceTable
|
|
||||||
.selectAll()
|
|
||||||
.where { SourceTable.id inList allSourceIds }
|
|
||||||
.mapNotNull { SourceType(it) }
|
|
||||||
.distinctBy { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteSourceMetasPayload(clientMutationId, allDeletedMetas, sources)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class FetchSourceMangaType {
|
enum class FetchSourceMangaType {
|
||||||
@@ -260,50 +252,48 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<DataFetcherResult<FetchSourceMangaPayload?>> {
|
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<FetchSourceMangaPayload?> {
|
||||||
val (clientMutationId, sourceId, type, page, query, filters) = input
|
val (clientMutationId, sourceId, type, page, query, filters) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
|
||||||
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
|
val mangasPage =
|
||||||
val mangasPage =
|
when (type) {
|
||||||
when (type) {
|
FetchSourceMangaType.SEARCH -> {
|
||||||
FetchSourceMangaType.SEARCH -> {
|
source.getSearchManga(
|
||||||
source.getSearchManga(
|
page = page,
|
||||||
page = page,
|
query = query.orEmpty(),
|
||||||
query = query.orEmpty(),
|
filters = updateFilterList(source, filters),
|
||||||
filters = updateFilterList(source, filters),
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
FetchSourceMangaType.POPULAR -> {
|
|
||||||
source.getPopularManga(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
FetchSourceMangaType.LATEST -> {
|
|
||||||
if (!source.supportsLatest) throw Exception("Source does not support latest")
|
|
||||||
source.getLatestUpdates(page)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val mangaIds = mangasPage.insertOrUpdate(sourceId)
|
FetchSourceMangaType.POPULAR -> {
|
||||||
|
source.getPopularManga(page)
|
||||||
val mangas =
|
|
||||||
transaction {
|
|
||||||
MangaTable
|
|
||||||
.selectAll()
|
|
||||||
.where { MangaTable.id inList mangaIds }
|
|
||||||
.map { MangaType(it) }
|
|
||||||
}.sortedBy {
|
|
||||||
mangaIds.indexOf(it.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchSourceMangaPayload(
|
FetchSourceMangaType.LATEST -> {
|
||||||
clientMutationId = clientMutationId,
|
if (!source.supportsLatest) throw Exception("Source does not support latest")
|
||||||
mangas = mangas,
|
source.getLatestUpdates(page)
|
||||||
hasNextPage = mangasPage.hasNextPage,
|
}
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
val mangaIds = mangasPage.insertOrUpdate(sourceId)
|
||||||
|
|
||||||
|
val mangas =
|
||||||
|
transaction {
|
||||||
|
MangaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { MangaTable.id inList mangaIds }
|
||||||
|
.map { MangaType(it) }
|
||||||
|
}.sortedBy {
|
||||||
|
mangaIds.indexOf(it.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchSourceMangaPayload(
|
||||||
|
clientMutationId = clientMutationId,
|
||||||
|
mangas = mangas,
|
||||||
|
hasNextPage = mangasPage.hasNextPage,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,29 +319,27 @@ class SourceMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateSourcePreference(input: UpdateSourcePreferenceInput): DataFetcherResult<UpdateSourcePreferencePayload?> {
|
fun updateSourcePreference(input: UpdateSourcePreferenceInput): UpdateSourcePreferencePayload? {
|
||||||
val (clientMutationId, sourceId, change) = input
|
val (clientMutationId, sourceId, change) = input
|
||||||
|
|
||||||
return asDataFetcherResult {
|
Source.setSourcePreference(sourceId, change.position, "") { preference ->
|
||||||
Source.setSourcePreference(sourceId, change.position, "") { preference ->
|
when (preference) {
|
||||||
when (preference) {
|
is SwitchPreferenceCompat -> change.switchState
|
||||||
is SwitchPreferenceCompat -> change.switchState
|
is CheckBoxPreference -> change.checkBoxState
|
||||||
is CheckBoxPreference -> change.checkBoxState
|
is EditTextPreference -> change.editTextState
|
||||||
is EditTextPreference -> change.editTextState
|
is ListPreference -> change.listState
|
||||||
is ListPreference -> change.listState
|
is MultiSelectListPreference -> change.multiSelectState?.toSet()
|
||||||
is MultiSelectListPreference -> change.multiSelectState?.toSet()
|
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
||||||
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
} ?: throw Exception("Expected change to ${preference::class.simpleName}")
|
||||||
} ?: throw Exception("Expected change to ${preference::class.simpleName}")
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateSourcePreferencePayload(
|
|
||||||
clientMutationId = clientMutationId,
|
|
||||||
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) },
|
|
||||||
source =
|
|
||||||
transaction {
|
|
||||||
SourceType(SourceTable.selectAll().where { SourceTable.id eq sourceId }.first())!!
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return UpdateSourcePreferencePayload(
|
||||||
|
clientMutationId = clientMutationId,
|
||||||
|
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) },
|
||||||
|
source =
|
||||||
|
transaction {
|
||||||
|
SourceType(SourceTable.selectAll().where { SourceTable.id eq sourceId }.first())!!
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
||||||
@@ -6,7 +8,6 @@ import graphql.execution.DataFetcherResult
|
|||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.TrackRecordType
|
import suwayomi.tachidesk.graphql.types.TrackRecordType
|
||||||
import suwayomi.tachidesk.graphql.types.TrackerType
|
import suwayomi.tachidesk.graphql.types.TrackerType
|
||||||
@@ -222,24 +223,22 @@ class TrackMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun trackProgress(input: TrackProgressInput): CompletableFuture<DataFetcherResult<TrackProgressPayload?>> {
|
fun trackProgress(input: TrackProgressInput): CompletableFuture<TrackProgressPayload?> {
|
||||||
val (clientMutationId, mangaId) = input
|
val (clientMutationId, mangaId) = input
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
Track.trackChapter(mangaId)
|
||||||
Track.trackChapter(mangaId)
|
val trackRecords =
|
||||||
val trackRecords =
|
transaction {
|
||||||
transaction {
|
TrackRecordTable
|
||||||
TrackRecordTable
|
.selectAll()
|
||||||
.selectAll()
|
.where { TrackRecordTable.mangaId eq mangaId }
|
||||||
.where { TrackRecordTable.mangaId eq mangaId }
|
.toList()
|
||||||
.toList()
|
}
|
||||||
}
|
TrackProgressPayload(
|
||||||
TrackProgressPayload(
|
clientMutationId,
|
||||||
clientMutationId,
|
trackRecords.map { TrackRecordType(it) },
|
||||||
trackRecords.map { TrackRecordType(it) },
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
|
||||||
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
import suwayomi.tachidesk.graphql.directives.RequireAuth
|
||||||
import suwayomi.tachidesk.graphql.types.LibraryUpdateStatus
|
import suwayomi.tachidesk.graphql.types.LibraryUpdateStatus
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateStatus
|
import suwayomi.tachidesk.graphql.types.UpdateStatus
|
||||||
@@ -28,7 +29,7 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateLibrary(input: UpdateLibraryInput): CompletableFuture<DataFetcherResult<UpdateLibraryPayload?>> {
|
fun updateLibrary(input: UpdateLibraryInput): CompletableFuture<UpdateLibraryPayload?> {
|
||||||
updater.addCategoriesToUpdateQueue(
|
updater.addCategoriesToUpdateQueue(
|
||||||
Category.getCategoryList().filter { input.categories?.contains(it.id) ?: true },
|
Category.getCategoryList().filter { input.categories?.contains(it.id) ?: true },
|
||||||
clear = true,
|
clear = true,
|
||||||
@@ -36,17 +37,15 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
UpdateLibraryPayload(
|
||||||
UpdateLibraryPayload(
|
input.clientMutationId,
|
||||||
input.clientMutationId,
|
updateStatus =
|
||||||
updateStatus =
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
LibraryUpdateStatus(
|
||||||
LibraryUpdateStatus(
|
updater.updates.first(),
|
||||||
updater.updates.first(),
|
)
|
||||||
)
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateLibraryManga(input: UpdateLibraryMangaInput): CompletableFuture<DataFetcherResult<UpdateLibraryMangaPayload?>> {
|
fun updateLibraryManga(input: UpdateLibraryMangaInput): CompletableFuture<UpdateLibraryMangaPayload?> {
|
||||||
updateLibrary(
|
updateLibrary(
|
||||||
UpdateLibraryInput(
|
UpdateLibraryInput(
|
||||||
clientMutationId = input.clientMutationId,
|
clientMutationId = input.clientMutationId,
|
||||||
@@ -69,15 +68,13 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
UpdateLibraryMangaPayload(
|
||||||
UpdateLibraryMangaPayload(
|
input.clientMutationId,
|
||||||
input.clientMutationId,
|
updateStatus =
|
||||||
updateStatus =
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
UpdateStatus(updater.status.first())
|
||||||
UpdateStatus(updater.status.first())
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +89,7 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@RequireAuth
|
@RequireAuth
|
||||||
fun updateCategoryManga(input: UpdateCategoryMangaInput): CompletableFuture<DataFetcherResult<UpdateCategoryMangaPayload?>> {
|
fun updateCategoryManga(input: UpdateCategoryMangaInput): CompletableFuture<UpdateCategoryMangaPayload?> {
|
||||||
updateLibrary(
|
updateLibrary(
|
||||||
UpdateLibraryInput(
|
UpdateLibraryInput(
|
||||||
clientMutationId = input.clientMutationId,
|
clientMutationId = input.clientMutationId,
|
||||||
@@ -101,15 +98,13 @@ class UpdateMutation {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return future {
|
return future {
|
||||||
asDataFetcherResult {
|
UpdateCategoryMangaPayload(
|
||||||
UpdateCategoryMangaPayload(
|
input.clientMutationId,
|
||||||
input.clientMutationId,
|
updateStatus =
|
||||||
updateStatus =
|
withTimeout(30.seconds) {
|
||||||
withTimeout(30.seconds) {
|
UpdateStatus(updater.status.first())
|
||||||
UpdateStatus(updater.status.first())
|
},
|
||||||
},
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("RedundantNullableReturnType", "unused")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.schema.DataFetchingEnvironment
|
import graphql.schema.DataFetchingEnvironment
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.expediagroup.graphql.server.execution.GraphQLRequestParser
|
|||||||
import com.expediagroup.graphql.server.types.GraphQLBatchRequest
|
import com.expediagroup.graphql.server.types.GraphQLBatchRequest
|
||||||
import com.expediagroup.graphql.server.types.GraphQLRequest
|
import com.expediagroup.graphql.server.types.GraphQLRequest
|
||||||
import com.expediagroup.graphql.server.types.GraphQLServerRequest
|
import com.expediagroup.graphql.server.types.GraphQLServerRequest
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import io.javalin.http.UploadedFile
|
import io.javalin.http.UploadedFile
|
||||||
import io.javalin.json.JavalinJackson
|
import io.javalin.json.JavalinJackson
|
||||||
@@ -19,11 +20,12 @@ import io.javalin.json.fromJsonString
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||||
val jsonMapper = JavalinJackson()
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
||||||
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
|
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
|
||||||
return try {
|
return try {
|
||||||
|
val jsonMapper = context.jsonMapper()
|
||||||
val contentType = context.contentType()
|
val contentType = context.contentType()
|
||||||
val formParam =
|
val formParam =
|
||||||
if (
|
if (
|
||||||
@@ -77,7 +79,8 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_: IOException) {
|
} catch (e: IOException) {
|
||||||
|
logger.error(e) { "Error when parsing request" }
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ package suwayomi.tachidesk.graphql.server
|
|||||||
import com.expediagroup.graphql.generator.execution.FlowSubscriptionExecutionStrategy
|
import com.expediagroup.graphql.generator.execution.FlowSubscriptionExecutionStrategy
|
||||||
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
||||||
import com.expediagroup.graphql.server.execution.GraphQLServer
|
import com.expediagroup.graphql.server.execution.GraphQLServer
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
||||||
import graphql.ExceptionWhileDataFetching
|
import graphql.ExceptionWhileDataFetching
|
||||||
import graphql.GraphQL
|
import graphql.GraphQL
|
||||||
import graphql.execution.AsyncExecutionStrategy
|
import graphql.execution.AsyncExecutionStrategy
|
||||||
@@ -27,6 +26,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import suwayomi.tachidesk.graphql.server.subscriptions.ApolloSubscriptionProtocolHandler
|
import suwayomi.tachidesk.graphql.server.subscriptions.ApolloSubscriptionProtocolHandler
|
||||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||||
|
import tools.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
|
||||||
class TachideskGraphQLServer(
|
class TachideskGraphQLServer(
|
||||||
requestParser: JavalinGraphQLRequestParser,
|
requestParser: JavalinGraphQLRequestParser,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ private class GraphqlCursorCoercing : Coercing<Cursor, String> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return Cursor(input.value)
|
return Cursor(input.value!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()
|
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ private class GraphqlDurationAsStringCoercing : Coercing<Duration, String> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
Duration.parse(input.value)
|
Duration.parse(input.value!!)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
throw CoercingParseLiteralException(
|
throw CoercingParseLiteralException(
|
||||||
"Invalid duration format: ${input.value}. Expected ISO-8601 duration string (e.g., 'PT30M', 'P1D')",
|
"Invalid duration format: ${input.value}. Expected ISO-8601 duration string (e.g., 'PT30M', 'P1D')",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ private class GraphqlLongAsStringCoercing : Coercing<Long, String> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return input.value.toLong()
|
return input.value!!.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()
|
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ package suwayomi.tachidesk.graphql.server.subscriptions
|
|||||||
|
|
||||||
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
||||||
import com.expediagroup.graphql.server.types.GraphQLRequest
|
import com.expediagroup.graphql.server.types.GraphQLRequest
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.module.kotlin.convertValue
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import io.javalin.http.Header
|
import io.javalin.http.Header
|
||||||
import io.javalin.websocket.WsContext
|
import io.javalin.websocket.WsContext
|
||||||
@@ -41,6 +38,9 @@ import suwayomi.tachidesk.server.JavalinSetup.Attribute
|
|||||||
import suwayomi.tachidesk.server.JavalinSetup.getAttributeOrSet
|
import suwayomi.tachidesk.server.JavalinSetup.getAttributeOrSet
|
||||||
import suwayomi.tachidesk.server.user.UserType
|
import suwayomi.tachidesk.server.user.UserType
|
||||||
import suwayomi.tachidesk.server.user.getUserFromToken
|
import suwayomi.tachidesk.server.user.getUserFromToken
|
||||||
|
import tools.jackson.databind.ObjectMapper
|
||||||
|
import tools.jackson.module.kotlin.convertValue
|
||||||
|
import tools.jackson.module.kotlin.readValue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the `graphql-transport-ws` protocol defined by Denis Badurina
|
* Implementation of the `graphql-transport-ws` protocol defined by Denis Badurina
|
||||||
|
|||||||
Reference in New Issue
Block a user