Complete source mutations (#567)

This commit is contained in:
Mitchell Syer
2023-06-05 09:19:03 -04:00
committed by GitHub
parent 300c0a8f35
commit 7c3eff2ba7
5 changed files with 161 additions and 7 deletions

View File

@@ -1,8 +1,112 @@
package suwayomi.tachidesk.graphql.mutations
/**
* TODO Mutations
* - Browse with filters
* - Configure settings
*/
class SourceMutation
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.graphql.types.MangaType
import suwayomi.tachidesk.graphql.types.PreferenceObject
import suwayomi.tachidesk.manga.impl.MangaList.insertOrGet
import suwayomi.tachidesk.manga.impl.Search
import suwayomi.tachidesk.manga.impl.Source
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.server.JavalinSetup.future
import java.util.concurrent.CompletableFuture
class SourceMutation {
enum class FetchSourceMangaType {
SEARCH,
POPULAR,
LATEST
}
data class FilterChange(
val position: Int,
val state: String
)
data class FetchSourceMangaInput(
val clientMutationId: String? = null,
val source: Long,
val type: FetchSourceMangaType,
val page: Int,
val query: String? = null,
val filters: List<FilterChange>? = null
)
data class FetchSourceMangaPayload(
val clientMutationId: String?,
val mangas: List<MangaType>,
val hasNextPage: Boolean
)
fun fetchSourceManga(
input: FetchSourceMangaInput
): CompletableFuture<FetchSourceMangaPayload> {
val (clientMutationId, sourceId, type, page, query, filters) = input
return future {
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
val mangasPage = when (type) {
FetchSourceMangaType.SEARCH -> {
source.fetchSearchManga(
page = page,
query = query.orEmpty(),
filters = Search.buildFilterList(
sourceId = sourceId,
changes = filters?.map { Search.FilterChange(it.position, it.state) }
.orEmpty()
)
).awaitSingle()
}
FetchSourceMangaType.POPULAR -> {
source.fetchPopularManga(page).awaitSingle()
}
FetchSourceMangaType.LATEST -> {
if (!source.supportsLatest) throw Exception("Source does not support latest")
source.fetchLatestUpdates(page).awaitSingle()
}
}
val mangaIds = mangasPage.insertOrGet(sourceId)
val mangas = transaction {
MangaTable.select { MangaTable.id inList mangaIds }
.map { MangaType(it) }
}.sortedBy {
mangaIds.indexOf(it.id)
}
FetchSourceMangaPayload(
clientMutationId = clientMutationId,
mangas = mangas,
hasNextPage = mangasPage.hasNextPage
)
}
}
data class SourcePreferenceChange(
val position: Int,
val state: String
)
data class UpdateSourcePreferenceInput(
val clientMutationId: String? = null,
val source: Long,
val change: SourcePreferenceChange
)
data class UpdateSourcePreferencePayload(
val clientMutationId: String?,
val preferences: List<PreferenceObject>
)
fun updateSourcePreference(
input: UpdateSourcePreferenceInput
): UpdateSourcePreferencePayload {
val (clientMutationId, sourceId, change) = input
Source.setSourcePreference(sourceId, Source.SourcePreferenceChange(change.position, change.state))
return UpdateSourcePreferencePayload(
clientMutationId = clientMutationId,
preferences = Source.getSourcePreferences(sourceId).map { PreferenceObject(it.type, it.props) }
)
}
}

View File

@@ -11,6 +11,7 @@ import com.expediagroup.graphql.generator.SchemaGeneratorConfig
import com.expediagroup.graphql.generator.TopLevelObject
import com.expediagroup.graphql.generator.hooks.FlowSubscriptionSchemaGeneratorHooks
import com.expediagroup.graphql.generator.toSchema
import graphql.scalars.ExtendedScalars
import graphql.schema.GraphQLType
import suwayomi.tachidesk.graphql.mutations.CategoryMutation
import suwayomi.tachidesk.graphql.mutations.ChapterMutation
@@ -35,6 +36,7 @@ class CustomSchemaGeneratorHooks : FlowSubscriptionSchemaGeneratorHooks() {
override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) {
Long::class -> GraphQLLongAsString // encode to string for JS
Cursor::class -> GraphQLCursor
Any::class -> ExtendedScalars.Json
else -> super.willGenerateGraphQLType(type)
}
}

View File

@@ -18,6 +18,8 @@ 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.impl.Search
import suwayomi.tachidesk.manga.impl.Source
import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
@@ -64,6 +66,14 @@ class SourceType(
fun extension(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionType> {
return dataFetchingEnvironment.getValueFromDataLoader<Long, ExtensionType>("ExtensionForSourceDataLoader", id)
}
fun preferences(): List<PreferenceObject> {
return Source.getSourcePreferences(id).map { PreferenceObject(it.type, it.props) }
}
fun filters(): List<FilterObject> {
return Search.getFilterList(id, false).map { FilterObject(it.type, it.filter) }
}
}
fun SourceType(row: ResultRow): SourceType? {
@@ -122,3 +132,13 @@ data class SourceNodeList(
}
}
}
data class PreferenceObject(
val type: String,
val props: Any
)
data class FilterObject(
val type: String,
val filter: Any
)

View File

@@ -44,6 +44,34 @@ object MangaList {
return mangasPage.processEntries(sourceId)
}
fun MangasPage.insertOrGet(sourceId: Long): List<Int> {
return transaction {
mangas.map { manga ->
val mangaEntry = MangaTable.select {
(MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId)
}.firstOrNull()
if (mangaEntry == null) { // create manga entry
MangaTable.insertAndGetId {
it[url] = manga.url
it[title] = manga.title
it[artist] = manga.artist
it[author] = manga.author
it[description] = manga.description
it[genre] = manga.genre
it[status] = manga.status
it[thumbnail_url] = manga.thumbnail_url
it[updateStrategy] = manga.update_strategy.name
it[sourceReference] = sourceId
}.value
} else {
mangaEntry[MangaTable.id].value
}
}
}
}
fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
val mangasPage = this
val mangaList = transaction {

View File

@@ -125,7 +125,7 @@ object Search {
return filterList
}
private fun buildFilterList(sourceId: Long, changes: List<FilterChange>): FilterList {
fun buildFilterList(sourceId: Long, changes: List<FilterChange>): FilterList {
val source = getCatalogueSourceOrStub(sourceId)
val filterList = source.getFilterList()
return updateFilterList(filterList, changes)