@@ -1,24 +1,29 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.minus
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.batchInsert
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.insertAndGetId
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
import suwayomi.tachidesk.graphql.types.CategoryMetaType
|
||||
import suwayomi.tachidesk.graphql.types.CategoryType
|
||||
import suwayomi.tachidesk.graphql.types.MangaType
|
||||
import suwayomi.tachidesk.manga.impl.Category
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.isEmpty
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.isNotEmpty
|
||||
import suwayomi.tachidesk.manga.model.dataclass.IncludeInUpdate
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryMetaTable
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
|
||||
/**
|
||||
* TODO Mutations
|
||||
* - Name
|
||||
* - Order
|
||||
* - Default
|
||||
* - Create
|
||||
* - Delete
|
||||
*/
|
||||
class CategoryMutation {
|
||||
data class SetCategoryMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
@@ -72,4 +77,324 @@ class CategoryMutation {
|
||||
|
||||
return DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
||||
}
|
||||
|
||||
data class UpdateCategoryPatch(
|
||||
val name: String? = null,
|
||||
val default: Boolean? = null,
|
||||
val includeInUpdate: IncludeInUpdate? = null
|
||||
)
|
||||
|
||||
data class UpdateCategoryPayload(
|
||||
val clientMutationId: String?,
|
||||
val category: CategoryType
|
||||
)
|
||||
data class UpdateCategoryInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateCategoryPatch
|
||||
)
|
||||
|
||||
data class UpdateCategoriesPayload(
|
||||
val clientMutationId: String?,
|
||||
val categories: List<CategoryType>
|
||||
)
|
||||
data class UpdateCategoriesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateCategoryPatch
|
||||
)
|
||||
|
||||
private fun updateCategories(ids: List<Int>, patch: UpdateCategoryPatch) {
|
||||
transaction {
|
||||
if (patch.name != null) {
|
||||
CategoryTable.update({ CategoryTable.id inList ids }) { update ->
|
||||
patch.name.also {
|
||||
update[name] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
if (patch.default != null) {
|
||||
CategoryTable.update({ CategoryTable.id inList ids }) { update ->
|
||||
patch.default.also {
|
||||
update[isDefault] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
if (patch.includeInUpdate != null) {
|
||||
CategoryTable.update({ CategoryTable.id inList ids }) { update ->
|
||||
patch.includeInUpdate.also {
|
||||
update[includeInUpdate] = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCategory(input: UpdateCategoryInput): UpdateCategoryPayload {
|
||||
val (clientMutationId, id, patch) = input
|
||||
|
||||
updateCategories(listOf(id), patch)
|
||||
|
||||
val category = transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateCategoryPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
category = category
|
||||
)
|
||||
}
|
||||
|
||||
fun updateCategories(input: UpdateCategoriesInput): UpdateCategoriesPayload {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
|
||||
updateCategories(ids, patch)
|
||||
|
||||
val categories = transaction {
|
||||
CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||
}
|
||||
|
||||
return UpdateCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
categories = categories
|
||||
)
|
||||
}
|
||||
|
||||
data class UpdateCategoryOrderPayload(
|
||||
val clientMutationId: String?,
|
||||
val categories: List<CategoryType>
|
||||
)
|
||||
data class UpdateCategoryOrderInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val position: Int
|
||||
)
|
||||
|
||||
fun updateCategoryOrder(input: UpdateCategoryOrderInput): UpdateCategoryOrderPayload {
|
||||
val (clientMutationId, categoryId, position) = input
|
||||
require(position > 0) {
|
||||
"'order' must not be <= 0"
|
||||
}
|
||||
|
||||
transaction {
|
||||
val currentOrder = CategoryTable
|
||||
.select { 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(
|
||||
val clientMutationId: String? = null,
|
||||
val name: String,
|
||||
val order: Int? = null,
|
||||
val default: Boolean? = null,
|
||||
val includeInUpdate: IncludeInUpdate? = null
|
||||
)
|
||||
data class CreateCategoryPayload(
|
||||
val clientMutationId: String?,
|
||||
val category: CategoryType
|
||||
)
|
||||
fun createCategory(
|
||||
input: CreateCategoryInput
|
||||
): CreateCategoryPayload {
|
||||
val (clientMutationId, name, order, default, includeInUpdate) = input
|
||||
transaction {
|
||||
require(CategoryTable.select { CategoryTable.name eq input.name }.isEmpty()) {
|
||||
"'name' must be unique"
|
||||
}
|
||||
}
|
||||
require(!name.equals(Category.DEFAULT_CATEGORY_NAME, ignoreCase = true)) {
|
||||
"'name' must not be ${Category.DEFAULT_CATEGORY_NAME}"
|
||||
}
|
||||
if (order != null) {
|
||||
require(order > 0) {
|
||||
"'order' must not be <= 0"
|
||||
}
|
||||
}
|
||||
|
||||
val category = transaction {
|
||||
if (order != null) {
|
||||
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return CreateCategoryPayload(clientMutationId, category)
|
||||
}
|
||||
|
||||
data class DeleteCategoryInput(
|
||||
val clientMutationId: String? = null,
|
||||
val categoryId: Int
|
||||
)
|
||||
data class DeleteCategoryPayload(
|
||||
val clientMutationId: String?,
|
||||
val category: CategoryType?,
|
||||
val mangas: List<MangaType>
|
||||
)
|
||||
fun deleteCategory(
|
||||
input: DeleteCategoryInput
|
||||
): DeleteCategoryPayload {
|
||||
val (clientMutationId, categoryId) = input
|
||||
if (categoryId == 0) { // Don't delete default category
|
||||
return DeleteCategoryPayload(
|
||||
clientMutationId,
|
||||
null,
|
||||
emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
val (category, mangas) = transaction {
|
||||
val category = CategoryTable.select { CategoryTable.id eq categoryId }
|
||||
.firstOrNull()
|
||||
|
||||
val mangas = transaction {
|
||||
MangaTable.innerJoin(CategoryMangaTable)
|
||||
.select { CategoryMangaTable.category eq categoryId }
|
||||
.map { MangaType(it) }
|
||||
}
|
||||
|
||||
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
if (category != null) {
|
||||
CategoryType(category)
|
||||
} else {
|
||||
null
|
||||
} to mangas
|
||||
}
|
||||
|
||||
return DeleteCategoryPayload(clientMutationId, category, mangas)
|
||||
}
|
||||
|
||||
data class UpdateMangaCategoriesPatch(
|
||||
val clearCategories: Boolean? = null,
|
||||
val addToCategories: List<Int>? = null,
|
||||
val removeFromCategories: List<Int>? = null
|
||||
)
|
||||
|
||||
data class UpdateMangaCategoriesPayload(
|
||||
val clientMutationId: String?,
|
||||
val manga: MangaType
|
||||
)
|
||||
data class UpdateMangaCategoriesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateMangaCategoriesPatch
|
||||
)
|
||||
|
||||
data class UpdateMangasCategoriesPayload(
|
||||
val clientMutationId: String?,
|
||||
val mangas: List<MangaType>
|
||||
)
|
||||
data class UpdateMangasCategoriesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateMangaCategoriesPatch
|
||||
)
|
||||
|
||||
private fun updateMangas(ids: List<Int>, patch: UpdateMangaCategoriesPatch) {
|
||||
transaction {
|
||||
if (patch.clearCategories == true) {
|
||||
CategoryMangaTable.deleteWhere { CategoryMangaTable.manga inList ids }
|
||||
} else if (!patch.removeFromCategories.isNullOrEmpty()) {
|
||||
CategoryMangaTable.deleteWhere {
|
||||
(CategoryMangaTable.manga inList ids) and (CategoryMangaTable.category inList patch.removeFromCategories)
|
||||
}
|
||||
}
|
||||
if (!patch.addToCategories.isNullOrEmpty()) {
|
||||
val newCategories = buildList {
|
||||
ids.forEach { mangaId ->
|
||||
patch.addToCategories.forEach { categoryId ->
|
||||
val existingMapping = CategoryMangaTable.select {
|
||||
(CategoryMangaTable.manga eq mangaId) and (CategoryMangaTable.category eq categoryId)
|
||||
}.isNotEmpty()
|
||||
|
||||
if (!existingMapping) {
|
||||
add(mangaId to categoryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CategoryMangaTable.batchInsert(newCategories) { (manga, category) ->
|
||||
this[CategoryMangaTable.manga] = manga
|
||||
this[CategoryMangaTable.category] = category
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMangaCategories(input: UpdateMangaCategoriesInput): UpdateMangaCategoriesPayload {
|
||||
val (clientMutationId, id, patch) = input
|
||||
|
||||
updateMangas(listOf(id), patch)
|
||||
|
||||
val manga = transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateMangaCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = manga
|
||||
)
|
||||
}
|
||||
|
||||
fun updateMangasCategories(input: UpdateMangasCategoriesInput): UpdateMangasCategoriesPayload {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
|
||||
updateMangas(ids, patch)
|
||||
|
||||
val mangas = transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
|
||||
return UpdateMangasCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* TODO Mutations
|
||||
* - Add to category
|
||||
* - Remove from category
|
||||
* - Download x(all = -1) chapters
|
||||
* - Delete read/all downloaded chapters
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@ import suwayomi.tachidesk.graphql.server.primitives.Edge
|
||||
import suwayomi.tachidesk.graphql.server.primitives.Node
|
||||
import suwayomi.tachidesk.graphql.server.primitives.NodeList
|
||||
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
|
||||
import suwayomi.tachidesk.manga.model.dataclass.IncludeInUpdate
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@@ -22,13 +23,15 @@ class CategoryType(
|
||||
val id: Int,
|
||||
val order: Int,
|
||||
val name: String,
|
||||
val default: Boolean
|
||||
val default: Boolean,
|
||||
val includeInUpdate: IncludeInUpdate
|
||||
) : Node {
|
||||
constructor(row: ResultRow) : this(
|
||||
row[CategoryTable.id].value,
|
||||
row[CategoryTable.order],
|
||||
row[CategoryTable.name],
|
||||
row[CategoryTable.isDefault]
|
||||
row[CategoryTable.isDefault],
|
||||
IncludeInUpdate.fromValue(row[CategoryTable.includeInUpdate])
|
||||
)
|
||||
|
||||
fun mangas(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaNodeList> {
|
||||
|
||||
@@ -84,7 +84,7 @@ object Category {
|
||||
}
|
||||
|
||||
/** make sure category order numbers starts from 1 and is consecutive */
|
||||
private fun normalizeCategories() {
|
||||
fun normalizeCategories() {
|
||||
transaction {
|
||||
CategoryTable.selectAll()
|
||||
.orderBy(CategoryTable.order to SortOrder.ASC)
|
||||
|
||||
@@ -9,6 +9,6 @@ package suwayomi.tachidesk.manga.impl.util.lang
|
||||
|
||||
import org.jetbrains.exposed.sql.Query
|
||||
|
||||
fun Query.isEmpty() = this.count() == 0L
|
||||
fun Query.isEmpty() = this.empty()
|
||||
|
||||
fun Query.isNotEmpty() = !this.isEmpty()
|
||||
|
||||
Reference in New Issue
Block a user