Add mutations to update multiple metas (#1874)
This commit is contained in:
@@ -1,13 +1,17 @@
|
|||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.minus
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.minus
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
import org.jetbrains.exposed.sql.insertAndGetId
|
import org.jetbrains.exposed.sql.insertAndGetId
|
||||||
|
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
|
||||||
@@ -16,6 +20,7 @@ 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
|
||||||
import suwayomi.tachidesk.graphql.types.MangaType
|
import suwayomi.tachidesk.graphql.types.MangaType
|
||||||
|
import suwayomi.tachidesk.graphql.types.MetaInput
|
||||||
import suwayomi.tachidesk.manga.impl.Category
|
import suwayomi.tachidesk.manga.impl.Category
|
||||||
import suwayomi.tachidesk.manga.impl.CategoryManga
|
import suwayomi.tachidesk.manga.impl.CategoryManga
|
||||||
import suwayomi.tachidesk.manga.impl.util.lang.isEmpty
|
import suwayomi.tachidesk.manga.impl.util.lang.isEmpty
|
||||||
@@ -88,6 +93,138 @@ class CategoryMutation {
|
|||||||
DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class SetCategoryMetasItem(
|
||||||
|
val categoryIds: List<Int>,
|
||||||
|
val metas: List<MetaInput>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetCategoryMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val items: List<SetCategoryMetasItem>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetCategoryMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<CategoryMetaType>,
|
||||||
|
val categories: List<CategoryType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun setCategoryMetas(input: SetCategoryMetasInput): DataFetcherResult<SetCategoryMetasPayload?> =
|
||||||
|
asDataFetcherResult {
|
||||||
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
|
val metaByCategoryId =
|
||||||
|
items
|
||||||
|
.flatMap { item ->
|
||||||
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
|
item.categoryIds.map { categoryId -> categoryId to metaMap }
|
||||||
|
}.groupBy({ it.first }, { it.second })
|
||||||
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
|
|
||||||
|
Category.modifyCategoriesMetas(metaByCategoryId)
|
||||||
|
|
||||||
|
val allCategoryIds = metaByCategoryId.keys
|
||||||
|
val allMetaKeys = metaByCategoryId.values.flatMap { item -> item.keys }.distinct()
|
||||||
|
|
||||||
|
val (updatedMetas, categories) =
|
||||||
|
transaction {
|
||||||
|
val updatedMetas =
|
||||||
|
CategoryMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { (CategoryMetaTable.ref inList allCategoryIds) and (CategoryMetaTable.key inList allMetaKeys) }
|
||||||
|
.map { CategoryMetaType(it) }
|
||||||
|
|
||||||
|
val categories =
|
||||||
|
CategoryTable
|
||||||
|
.selectAll()
|
||||||
|
.where { CategoryTable.id inList allCategoryIds }
|
||||||
|
.map { CategoryType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
|
||||||
|
updatedMetas to categories
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCategoryMetasPayload(clientMutationId, updatedMetas, categories)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DeleteCategoryMetasItem(
|
||||||
|
val categoryIds: List<Int>,
|
||||||
|
val keys: List<String>? = null,
|
||||||
|
val prefixes: List<String>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteCategoryMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val items: List<DeleteCategoryMetasItem>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteCategoryMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<CategoryMetaType>,
|
||||||
|
val categories: List<CategoryType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun deleteCategoryMetas(input: DeleteCategoryMetasInput): DataFetcherResult<DeleteCategoryMetasPayload?> =
|
||||||
|
asDataFetcherResult {
|
||||||
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
|
"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 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,
|
||||||
val default: Boolean? = null,
|
val default: Boolean? = null,
|
||||||
|
|||||||
@@ -4,9 +4,14 @@ 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
|
||||||
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
|
import org.jetbrains.exposed.sql.or
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
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
|
||||||
@@ -15,6 +20,7 @@ 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
|
||||||
|
import suwayomi.tachidesk.graphql.types.MetaInput
|
||||||
import suwayomi.tachidesk.graphql.types.SyncConflictInfoType
|
import suwayomi.tachidesk.graphql.types.SyncConflictInfoType
|
||||||
import suwayomi.tachidesk.manga.impl.Chapter
|
import suwayomi.tachidesk.manga.impl.Chapter
|
||||||
import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReadyById
|
import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReadyById
|
||||||
@@ -246,6 +252,138 @@ class ChapterMutation {
|
|||||||
DeleteChapterMetaPayload(clientMutationId, meta, chapter)
|
DeleteChapterMetaPayload(clientMutationId, meta, chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class SetChapterMetasItem(
|
||||||
|
val chapterIds: List<Int>,
|
||||||
|
val metas: List<MetaInput>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetChapterMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val items: List<SetChapterMetasItem>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetChapterMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<ChapterMetaType>,
|
||||||
|
val chapters: List<ChapterType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun setChapterMetas(input: SetChapterMetasInput): DataFetcherResult<SetChapterMetasPayload?> =
|
||||||
|
asDataFetcherResult {
|
||||||
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
|
val metaByChapterId =
|
||||||
|
items
|
||||||
|
.flatMap { item ->
|
||||||
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
|
item.chapterIds.map { chapterId -> chapterId to metaMap }
|
||||||
|
}.groupBy({ it.first }, { it.second })
|
||||||
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
|
|
||||||
|
Chapter.modifyChaptersMetas(metaByChapterId)
|
||||||
|
|
||||||
|
val allChapterIds = metaByChapterId.keys
|
||||||
|
val allMetaKeys = metaByChapterId.values.flatMap { it.keys }.distinct()
|
||||||
|
|
||||||
|
val (updatedMetas, chapters) =
|
||||||
|
transaction {
|
||||||
|
val updatedMetas =
|
||||||
|
ChapterMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { (ChapterMetaTable.ref inList allChapterIds) and (ChapterMetaTable.key inList allMetaKeys) }
|
||||||
|
.map { ChapterMetaType(it) }
|
||||||
|
|
||||||
|
val chapters =
|
||||||
|
ChapterTable
|
||||||
|
.selectAll()
|
||||||
|
.where { ChapterTable.id inList allChapterIds }
|
||||||
|
.map { ChapterType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
|
||||||
|
updatedMetas to chapters
|
||||||
|
}
|
||||||
|
|
||||||
|
SetChapterMetasPayload(clientMutationId, updatedMetas, chapters)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DeleteChapterMetasItem(
|
||||||
|
val chapterIds: List<Int>,
|
||||||
|
val keys: List<String>? = null,
|
||||||
|
val prefixes: List<String>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteChapterMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val items: List<DeleteChapterMetasItem>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteChapterMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<ChapterMetaType>,
|
||||||
|
val chapters: List<ChapterType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun deleteChapterMetas(input: DeleteChapterMetasInput): DataFetcherResult<DeleteChapterMetasPayload?> =
|
||||||
|
asDataFetcherResult {
|
||||||
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
|
items.forEach { item ->
|
||||||
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
|
"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 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,
|
||||||
val chapterId: Int,
|
val chapterId: Int,
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
|
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
|
||||||
@@ -11,6 +16,7 @@ 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
|
||||||
|
import suwayomi.tachidesk.graphql.types.MetaInput
|
||||||
import suwayomi.tachidesk.manga.impl.Library
|
import suwayomi.tachidesk.manga.impl.Library
|
||||||
import suwayomi.tachidesk.manga.impl.Manga
|
import suwayomi.tachidesk.manga.impl.Manga
|
||||||
import suwayomi.tachidesk.manga.impl.update.IUpdater
|
import suwayomi.tachidesk.manga.impl.update.IUpdater
|
||||||
@@ -226,4 +232,138 @@ class MangaMutation {
|
|||||||
DeleteMangaMetaPayload(clientMutationId, meta, manga)
|
DeleteMangaMetaPayload(clientMutationId, meta, manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class SetMangaMetasItem(
|
||||||
|
val mangaIds: List<Int>,
|
||||||
|
val metas: List<MetaInput>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetMangaMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val items: List<SetMangaMetasItem>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetMangaMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<MangaMetaType>,
|
||||||
|
val mangas: List<MangaType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun setMangaMetas(input: SetMangaMetasInput): DataFetcherResult<SetMangaMetasPayload?> {
|
||||||
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
|
return asDataFetcherResult {
|
||||||
|
val metaByMangaId =
|
||||||
|
items
|
||||||
|
.flatMap { item ->
|
||||||
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
|
item.mangaIds.map { mangaId -> mangaId to metaMap }
|
||||||
|
}.groupBy({ it.first }, { it.second })
|
||||||
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
|
|
||||||
|
Manga.modifyMangasMetas(metaByMangaId)
|
||||||
|
|
||||||
|
val allMangaIds = metaByMangaId.keys
|
||||||
|
val allMetaKeys = metaByMangaId.values.flatMap { it.keys }.distinct()
|
||||||
|
|
||||||
|
val (updatedMetas, mangas) =
|
||||||
|
transaction {
|
||||||
|
val updatedMetas =
|
||||||
|
MangaMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { (MangaMetaTable.ref inList allMangaIds) and (MangaMetaTable.key inList allMetaKeys) }
|
||||||
|
.map { MangaMetaType(it) }
|
||||||
|
|
||||||
|
val mangas =
|
||||||
|
MangaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { MangaTable.id inList allMangaIds }
|
||||||
|
.map { MangaType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
|
||||||
|
updatedMetas to mangas
|
||||||
|
}
|
||||||
|
|
||||||
|
SetMangaMetasPayload(clientMutationId, updatedMetas, mangas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DeleteMangaMetasItem(
|
||||||
|
val mangaIds: List<Int>,
|
||||||
|
val keys: List<String>? = null,
|
||||||
|
val prefixes: List<String>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteMangaMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val items: List<DeleteMangaMetasItem>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteMangaMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<MangaMetaType>,
|
||||||
|
val mangas: List<MangaType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun deleteMangaMetas(input: DeleteMangaMetasInput): DataFetcherResult<DeleteMangaMetasPayload?> {
|
||||||
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
|
return asDataFetcherResult {
|
||||||
|
items.forEach { item ->
|
||||||
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
|
"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 mangas =
|
||||||
|
transaction {
|
||||||
|
MangaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { MangaTable.id inList allMangaIds }
|
||||||
|
.map { MangaType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteMangaMetasPayload(clientMutationId, allDeletedMetas, mangas)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
package suwayomi.tachidesk.graphql.mutations
|
package suwayomi.tachidesk.graphql.mutations
|
||||||
|
|
||||||
import graphql.execution.DataFetcherResult
|
import graphql.execution.DataFetcherResult
|
||||||
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
|
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.global.impl.GlobalMeta
|
import suwayomi.tachidesk.global.impl.GlobalMeta
|
||||||
@@ -10,6 +15,7 @@ import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
|||||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
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
|
||||||
|
|
||||||
class MetaMutation {
|
class MetaMutation {
|
||||||
data class SetGlobalMetaInput(
|
data class SetGlobalMetaInput(
|
||||||
@@ -68,4 +74,86 @@ class MetaMutation {
|
|||||||
DeleteGlobalMetaPayload(clientMutationId, meta)
|
DeleteGlobalMetaPayload(clientMutationId, meta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class SetGlobalMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val metas: List<MetaInput>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetGlobalMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<GlobalMetaType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun setGlobalMetas(input: SetGlobalMetasInput): DataFetcherResult<SetGlobalMetasPayload?> {
|
||||||
|
val (clientMutationId, metas) = input
|
||||||
|
|
||||||
|
return asDataFetcherResult {
|
||||||
|
val metaMap = metas.associate { it.key to it.value }
|
||||||
|
GlobalMeta.modifyMetas(metaMap)
|
||||||
|
|
||||||
|
val updatedMetas =
|
||||||
|
transaction {
|
||||||
|
GlobalMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { GlobalMetaTable.key inList metaMap.keys }
|
||||||
|
.map { GlobalMetaType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
SetGlobalMetasPayload(clientMutationId, updatedMetas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DeleteGlobalMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val keys: List<String>? = null,
|
||||||
|
val prefixes: List<String>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteGlobalMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<GlobalMetaType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun deleteGlobalMetas(input: DeleteGlobalMetasInput): DataFetcherResult<DeleteGlobalMetasPayload?> {
|
||||||
|
val (clientMutationId, keys, prefixes) = input
|
||||||
|
|
||||||
|
return asDataFetcherResult {
|
||||||
|
require(!keys.isNullOrEmpty() || !prefixes.isNullOrEmpty()) {
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteGlobalMetasPayload(clientMutationId, metas)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,21 @@ 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 graphql.execution.DataFetcherResult
|
||||||
|
import org.jetbrains.exposed.sql.LikePattern
|
||||||
|
import org.jetbrains.exposed.sql.Op
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
|
||||||
import org.jetbrains.exposed.sql.and
|
import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.deleteWhere
|
import org.jetbrains.exposed.sql.deleteWhere
|
||||||
|
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.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
|
||||||
|
import suwayomi.tachidesk.graphql.types.MetaInput
|
||||||
import suwayomi.tachidesk.graphql.types.Preference
|
import suwayomi.tachidesk.graphql.types.Preference
|
||||||
import suwayomi.tachidesk.graphql.types.SourceMetaType
|
import suwayomi.tachidesk.graphql.types.SourceMetaType
|
||||||
import suwayomi.tachidesk.graphql.types.SourceType
|
import suwayomi.tachidesk.graphql.types.SourceType
|
||||||
@@ -98,6 +104,140 @@ class SourceMutation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class SetSourceMetasItem(
|
||||||
|
val sourceIds: List<Long>,
|
||||||
|
val metas: List<MetaInput>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetSourceMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val items: List<SetSourceMetasItem>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SetSourceMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<SourceMetaType>,
|
||||||
|
val sources: List<SourceType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun setSourceMetas(input: SetSourceMetasInput): DataFetcherResult<SetSourceMetasPayload?> {
|
||||||
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
|
return asDataFetcherResult {
|
||||||
|
val metaBySourceId =
|
||||||
|
items
|
||||||
|
.flatMap { item ->
|
||||||
|
val metaMap = item.metas.associate { it.key to it.value }
|
||||||
|
item.sourceIds.map { sourceId -> sourceId to metaMap }
|
||||||
|
}.groupBy({ it.first }, { it.second })
|
||||||
|
.mapValues { (_, maps) -> maps.reduce { acc, map -> acc + map } }
|
||||||
|
|
||||||
|
Source.modifySourceMetas(metaBySourceId)
|
||||||
|
|
||||||
|
val allSourceIds = metaBySourceId.keys
|
||||||
|
val allMetaKeys = metaBySourceId.values.flatMap { it.keys }.distinct()
|
||||||
|
|
||||||
|
val (updatedMetas, sources) =
|
||||||
|
transaction {
|
||||||
|
val updatedMetas =
|
||||||
|
SourceMetaTable
|
||||||
|
.selectAll()
|
||||||
|
.where { (SourceMetaTable.ref inList allSourceIds) and (SourceMetaTable.key inList allMetaKeys) }
|
||||||
|
.map { SourceMetaType(it) }
|
||||||
|
|
||||||
|
val sources =
|
||||||
|
SourceTable
|
||||||
|
.selectAll()
|
||||||
|
.where { SourceTable.id inList allSourceIds }
|
||||||
|
.mapNotNull { SourceType(it) }
|
||||||
|
.distinctBy { it.id }
|
||||||
|
|
||||||
|
updatedMetas to sources
|
||||||
|
}
|
||||||
|
|
||||||
|
SetSourceMetasPayload(clientMutationId, updatedMetas, sources)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DeleteSourceMetasItem(
|
||||||
|
val sourceIds: List<Long>,
|
||||||
|
val keys: List<String>? = null,
|
||||||
|
val prefixes: List<String>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteSourceMetasInput(
|
||||||
|
val clientMutationId: String? = null,
|
||||||
|
val items: List<DeleteSourceMetasItem>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DeleteSourceMetasPayload(
|
||||||
|
val clientMutationId: String?,
|
||||||
|
val metas: List<SourceMetaType>,
|
||||||
|
val sources: List<SourceType>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RequireAuth
|
||||||
|
fun deleteSourceMetas(input: DeleteSourceMetasInput): DataFetcherResult<DeleteSourceMetasPayload?> {
|
||||||
|
val (clientMutationId, items) = input
|
||||||
|
|
||||||
|
return asDataFetcherResult {
|
||||||
|
items.forEach { item ->
|
||||||
|
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
|
||||||
|
"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 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 {
|
||||||
SEARCH,
|
SEARCH,
|
||||||
POPULAR,
|
POPULAR,
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ interface MetaType : Node {
|
|||||||
val value: String
|
val value: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class MetaInput(
|
||||||
|
val key: String,
|
||||||
|
val value: String,
|
||||||
|
)
|
||||||
|
|
||||||
class ChapterMetaType(
|
class ChapterMetaType(
|
||||||
override val key: String,
|
override val key: String,
|
||||||
override val value: String,
|
override val value: String,
|
||||||
|
|||||||
Reference in New Issue
Block a user