Basic JWT implementation (#1524)

* Basic JWT implementation

* Move JWT to UI_LOGIN mode and bring back SIMPLE_LOGIN as before

* Update server/src/main/kotlin/suwayomi/tachidesk/global/impl/util/Jwt.kt

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>

* Refresh: Update only access token

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>

* Implement JWT Audience

* Store JWT key

Generates the key on startup if not set

* Handle invalid Base64

* Make JWT expiry configurable

* Missing value parse

* Update server/src/main/kotlin/suwayomi/tachidesk/global/impl/util/Jwt.kt

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>

* Simplify Duration parsing

* JWT Protect Mutations

* JWT Protect Queries and Subscriptions

* JWT Protect v1 WebSockets

* WebSockets allow sending token via protocol header

* Also respect the `suwayomi-server-token` cookie

* JWT reduce default token expiry

* JWT Support cookie on WebSocket as well

* Lint

* Authenticate graphql subscription via connection_init payload

* WebView: Prefer explicit token over cookie

This hack was implemented because WebView sent `"null"` if no token was
supplied, just don't send a bad token, then we can do this properly

* WebView: Implement basic login dialog if no token supplied

---------

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
Co-authored-by: schroda <50052685+schroda@users.noreply.github.com>
This commit is contained in:
Constantin Piber
2025-08-21 00:04:48 +02:00
committed by GitHub
parent d90bfb6e3e
commit 8547159eec
60 changed files with 1567 additions and 410 deletions

View File

@@ -1,16 +1,21 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.schema.DataFetchingEnvironment
import io.javalin.http.UploadedFile
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import suwayomi.tachidesk.graphql.server.TemporaryFileStorage
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.BackupRestoreStatus
import suwayomi.tachidesk.graphql.types.toStatus
import suwayomi.tachidesk.manga.impl.backup.BackupFlags
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupExport
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupImport
import suwayomi.tachidesk.manga.impl.backup.proto.models.Backup
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import java.util.concurrent.CompletableFuture
import kotlin.time.Duration.Companion.seconds
@@ -26,7 +31,11 @@ class BackupMutation {
val status: BackupRestoreStatus?,
)
fun restoreBackup(input: RestoreBackupInput): CompletableFuture<RestoreBackupPayload> {
fun restoreBackup(
dataFetchingEnvironment: DataFetchingEnvironment,
input: RestoreBackupInput,
): CompletableFuture<RestoreBackupPayload> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, backup) = input
return future {
@@ -53,7 +62,11 @@ class BackupMutation {
val url: String,
)
fun createBackup(input: CreateBackupInput? = null): CreateBackupPayload {
fun createBackup(
dataFetchingEnvironment: DataFetchingEnvironment,
input: CreateBackupInput? = null,
): CreateBackupPayload {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val filename = Backup.getFilename()
val backup =

View File

@@ -1,6 +1,7 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
import org.jetbrains.exposed.sql.SqlExpressionBuilder.minus
@@ -12,6 +13,7 @@ import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.CategoryMetaType
import suwayomi.tachidesk.graphql.types.CategoryType
import suwayomi.tachidesk.graphql.types.MangaType
@@ -23,6 +25,9 @@ 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
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
class CategoryMutation {
data class SetCategoryMetaInput(
@@ -35,8 +40,12 @@ class CategoryMutation {
val meta: CategoryMetaType,
)
fun setCategoryMeta(input: SetCategoryMetaInput): DataFetcherResult<SetCategoryMetaPayload?> =
fun setCategoryMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: SetCategoryMetaInput,
): DataFetcherResult<SetCategoryMetaPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, meta) = input
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
@@ -56,8 +65,12 @@ class CategoryMutation {
val category: CategoryType,
)
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DataFetcherResult<DeleteCategoryMetaPayload?> =
fun deleteCategoryMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DeleteCategoryMetaInput,
): DataFetcherResult<DeleteCategoryMetaPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, categoryId, key) = input
val (meta, category) =
@@ -150,8 +163,12 @@ class CategoryMutation {
}
}
fun updateCategory(input: UpdateCategoryInput): DataFetcherResult<UpdateCategoryPayload?> =
fun updateCategory(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateCategoryInput,
): DataFetcherResult<UpdateCategoryPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, id, patch) = input
updateCategories(listOf(id), patch)
@@ -167,8 +184,12 @@ class CategoryMutation {
)
}
fun updateCategories(input: UpdateCategoriesInput): DataFetcherResult<UpdateCategoriesPayload?> =
fun updateCategories(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateCategoriesInput,
): DataFetcherResult<UpdateCategoriesPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, ids, patch) = input
updateCategories(ids, patch)
@@ -195,8 +216,12 @@ class CategoryMutation {
val position: Int,
)
fun updateCategoryOrder(input: UpdateCategoryOrderInput): DataFetcherResult<UpdateCategoryOrderPayload?> =
fun updateCategoryOrder(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateCategoryOrderInput,
): DataFetcherResult<UpdateCategoryOrderPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, categoryId, position) = input
require(position > 0) {
"'order' must not be <= 0"
@@ -253,8 +278,12 @@ class CategoryMutation {
val category: CategoryType,
)
fun createCategory(input: CreateCategoryInput): DataFetcherResult<CreateCategoryPayload?> =
fun createCategory(
dataFetchingEnvironment: DataFetchingEnvironment,
input: CreateCategoryInput,
): DataFetcherResult<CreateCategoryPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, name, order, default, includeInUpdate, includeInDownload) = input
transaction {
require(CategoryTable.selectAll().where { CategoryTable.name eq input.name }.isEmpty()) {
@@ -312,8 +341,12 @@ class CategoryMutation {
val mangas: List<MangaType>,
)
fun deleteCategory(input: DeleteCategoryInput): DataFetcherResult<DeleteCategoryPayload?> {
fun deleteCategory(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DeleteCategoryInput,
): DataFetcherResult<DeleteCategoryPayload?> {
return asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, categoryId) = input
if (categoryId == 0) { // Don't delete default category
return@asDataFetcherResult DeleteCategoryPayload(
@@ -401,8 +434,12 @@ class CategoryMutation {
}
}
fun updateMangaCategories(input: UpdateMangaCategoriesInput): DataFetcherResult<UpdateMangaCategoriesPayload?> =
fun updateMangaCategories(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateMangaCategoriesInput,
): DataFetcherResult<UpdateMangaCategoriesPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, id, patch) = input
updateMangas(listOf(id), patch)
@@ -418,8 +455,12 @@ class CategoryMutation {
)
}
fun updateMangasCategories(input: UpdateMangasCategoriesInput): DataFetcherResult<UpdateMangasCategoriesPayload?> =
fun updateMangasCategories(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateMangasCategoriesInput,
): DataFetcherResult<UpdateMangasCategoriesPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, ids, patch) = input
updateMangas(ids, patch)

View File

@@ -1,6 +1,7 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.exposed.dao.id.EntityID
@@ -12,6 +13,7 @@ import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.ChapterMetaType
import suwayomi.tachidesk.graphql.types.ChapterType
import suwayomi.tachidesk.graphql.types.SyncConflictInfoType
@@ -20,7 +22,10 @@ import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReadyById
import suwayomi.tachidesk.manga.impl.sync.KoreaderSyncService
import suwayomi.tachidesk.manga.model.table.ChapterMetaTable
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import java.net.URLEncoder
import java.time.Instant
import java.util.concurrent.CompletableFuture
@@ -112,8 +117,12 @@ class ChapterMutation {
}
}
fun updateChapter(input: UpdateChapterInput): DataFetcherResult<UpdateChapterPayload?> =
fun updateChapter(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateChapterInput,
): DataFetcherResult<UpdateChapterPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, id, patch) = input
updateChapters(listOf(id), patch)
@@ -129,8 +138,12 @@ class ChapterMutation {
)
}
fun updateChapters(input: UpdateChaptersInput): DataFetcherResult<UpdateChaptersPayload?> =
fun updateChapters(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateChaptersInput,
): DataFetcherResult<UpdateChaptersPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, ids, patch) = input
updateChapters(ids, patch)
@@ -156,7 +169,11 @@ class ChapterMutation {
val chapters: List<ChapterType>,
)
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<DataFetcherResult<FetchChaptersPayload?>> {
fun fetchChapters(
dataFetchingEnvironment: DataFetchingEnvironment,
input: FetchChaptersInput,
): CompletableFuture<DataFetcherResult<FetchChaptersPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, mangaId) = input
return future {
@@ -190,8 +207,12 @@ class ChapterMutation {
val meta: ChapterMetaType,
)
fun setChapterMeta(input: SetChapterMetaInput): DataFetcherResult<SetChapterMetaPayload?> =
fun setChapterMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: SetChapterMetaInput,
): DataFetcherResult<SetChapterMetaPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, meta) = input
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
@@ -211,8 +232,12 @@ class ChapterMutation {
val chapter: ChapterType,
)
fun deleteChapterMeta(input: DeleteChapterMetaInput): DataFetcherResult<DeleteChapterMetaPayload?> =
fun deleteChapterMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DeleteChapterMetaInput,
): DataFetcherResult<DeleteChapterMetaPayload?> =
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, chapterId, key) = input
val (meta, chapter) =
@@ -260,7 +285,11 @@ class ChapterMutation {
val syncConflict: SyncConflictInfoType?,
)
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<DataFetcherResult<FetchChapterPagesPayload?>> {
fun fetchChapterPages(
dataFetchingEnvironment: DataFetchingEnvironment,
input: FetchChapterPagesInput,
): CompletableFuture<DataFetcherResult<FetchChapterPagesPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, chapterId) = input
val paramsMap = input.toParams()

View File

@@ -1,11 +1,13 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.ChapterType
import suwayomi.tachidesk.graphql.types.DownloadStatus
import suwayomi.tachidesk.manga.impl.Chapter
@@ -13,7 +15,10 @@ import suwayomi.tachidesk.manga.impl.download.DownloadManager
import suwayomi.tachidesk.manga.impl.download.model.DownloadUpdateType.DEQUEUED
import suwayomi.tachidesk.manga.impl.download.model.Status
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import java.util.concurrent.CompletableFuture
import kotlin.time.Duration.Companion.seconds
@@ -28,7 +33,11 @@ class DownloadMutation {
val chapters: List<ChapterType>,
)
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DataFetcherResult<DeleteDownloadedChaptersPayload?> {
fun deleteDownloadedChapters(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DeleteDownloadedChaptersInput,
): DataFetcherResult<DeleteDownloadedChaptersPayload?> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, chapters) = input
return asDataFetcherResult {
@@ -57,7 +66,11 @@ class DownloadMutation {
val chapters: ChapterType,
)
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DataFetcherResult<DeleteDownloadedChapterPayload?> {
fun deleteDownloadedChapter(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DeleteDownloadedChapterInput,
): DataFetcherResult<DeleteDownloadedChapterPayload?> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, chapter) = input
return asDataFetcherResult {
@@ -84,8 +97,10 @@ class DownloadMutation {
)
fun enqueueChapterDownloads(
dataFetchingEnvironment: DataFetchingEnvironment,
input: EnqueueChapterDownloadsInput,
): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadsPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, chapters) = input
return future {
@@ -118,7 +133,11 @@ class DownloadMutation {
val downloadStatus: DownloadStatus,
)
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadPayload?>> {
fun enqueueChapterDownload(
dataFetchingEnvironment: DataFetchingEnvironment,
input: EnqueueChapterDownloadInput,
): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, chapter) = input
return future {
@@ -151,8 +170,10 @@ class DownloadMutation {
)
fun dequeueChapterDownloads(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DequeueChapterDownloadsInput,
): CompletableFuture<DataFetcherResult<DequeueChapterDownloadsPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, chapters) = input
return future {
@@ -187,7 +208,11 @@ class DownloadMutation {
val downloadStatus: DownloadStatus,
)
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DataFetcherResult<DequeueChapterDownloadPayload?>> {
fun dequeueChapterDownload(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DequeueChapterDownloadInput,
): CompletableFuture<DataFetcherResult<DequeueChapterDownloadPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, chapter) = input
return future {
@@ -221,9 +246,13 @@ class DownloadMutation {
val downloadStatus: DownloadStatus,
)
fun startDownloader(input: StartDownloaderInput): CompletableFuture<DataFetcherResult<StartDownloaderPayload?>> =
fun startDownloader(
dataFetchingEnvironment: DataFetchingEnvironment,
input: StartDownloaderInput,
): CompletableFuture<DataFetcherResult<StartDownloaderPayload?>> =
future {
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
DownloadManager.start()
StartDownloaderPayload(
@@ -249,9 +278,13 @@ class DownloadMutation {
val downloadStatus: DownloadStatus,
)
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<DataFetcherResult<StopDownloaderPayload?>> =
fun stopDownloader(
dataFetchingEnvironment: DataFetchingEnvironment,
input: StopDownloaderInput,
): CompletableFuture<DataFetcherResult<StopDownloaderPayload?>> =
future {
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
DownloadManager.stop()
StopDownloaderPayload(
@@ -277,9 +310,13 @@ class DownloadMutation {
val downloadStatus: DownloadStatus,
)
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<DataFetcherResult<ClearDownloaderPayload?>> =
fun clearDownloader(
dataFetchingEnvironment: DataFetchingEnvironment,
input: ClearDownloaderInput,
): CompletableFuture<DataFetcherResult<ClearDownloaderPayload?>> =
future {
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
DownloadManager.clear()
ClearDownloaderPayload(
@@ -307,7 +344,11 @@ class DownloadMutation {
val downloadStatus: DownloadStatus,
)
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<DataFetcherResult<ReorderChapterDownloadPayload?>> {
fun reorderChapterDownload(
dataFetchingEnvironment: DataFetchingEnvironment,
input: ReorderChapterDownloadInput,
): CompletableFuture<DataFetcherResult<ReorderChapterDownloadPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, chapter, to) = input
return future {

View File

@@ -2,15 +2,20 @@ package suwayomi.tachidesk.graphql.mutations
import eu.kanade.tachiyomi.source.local.LocalSource
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import io.javalin.http.UploadedFile
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.ExtensionType
import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList
import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import java.util.concurrent.CompletableFuture
class ExtensionMutation {
@@ -73,7 +78,11 @@ class ExtensionMutation {
}
}
fun updateExtension(input: UpdateExtensionInput): CompletableFuture<DataFetcherResult<UpdateExtensionPayload?>> {
fun updateExtension(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateExtensionInput,
): CompletableFuture<DataFetcherResult<UpdateExtensionPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, id, patch) = input
return future {
@@ -97,7 +106,11 @@ class ExtensionMutation {
}
}
fun updateExtensions(input: UpdateExtensionsInput): CompletableFuture<DataFetcherResult<UpdateExtensionsPayload?>> {
fun updateExtensions(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateExtensionsInput,
): CompletableFuture<DataFetcherResult<UpdateExtensionsPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, ids, patch) = input
return future {
@@ -129,7 +142,11 @@ class ExtensionMutation {
val extensions: List<ExtensionType>,
)
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<DataFetcherResult<FetchExtensionsPayload?>> {
fun fetchExtensions(
dataFetchingEnvironment: DataFetchingEnvironment,
input: FetchExtensionsInput,
): CompletableFuture<DataFetcherResult<FetchExtensionsPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId) = input
return future {
@@ -163,8 +180,10 @@ class ExtensionMutation {
)
fun installExternalExtension(
dataFetchingEnvironment: DataFetchingEnvironment,
input: InstallExternalExtensionInput,
): CompletableFuture<DataFetcherResult<InstallExternalExtensionPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, extensionFile) = input
return future {

View File

@@ -1,7 +1,12 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.schema.DataFetchingEnvironment
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
import suwayomi.tachidesk.server.ApplicationDirs
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import uy.kohesive.injekt.injectLazy
private val applicationDirs: ApplicationDirs by injectLazy()
@@ -21,7 +26,11 @@ class ImageMutation {
val cachedPages: Boolean?,
)
fun clearCachedImages(input: ClearCachedImagesInput): ClearCachedImagesPayload {
fun clearCachedImages(
dataFetchingEnvironment: DataFetchingEnvironment,
input: ClearCachedImagesInput,
): ClearCachedImagesPayload {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, downloadedThumbnails, cachedThumbnails, cachedPages) = input
val downloadedThumbnailsResult =

View File

@@ -1,15 +1,20 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
import suwayomi.tachidesk.graphql.types.UpdateState.ERROR
import suwayomi.tachidesk.graphql.types.UpdateState.IDLE
import suwayomi.tachidesk.graphql.types.WebUIFlavor
import suwayomi.tachidesk.graphql.types.WebUIUpdateStatus
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import suwayomi.tachidesk.server.util.WebInterfaceManager
import java.util.concurrent.CompletableFuture
import kotlin.time.Duration.Companion.seconds
@@ -24,9 +29,13 @@ class InfoMutation {
val updateStatus: WebUIUpdateStatus,
)
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<DataFetcherResult<WebUIUpdatePayload?>> {
fun updateWebUI(
dataFetchingEnvironment: DataFetchingEnvironment,
input: WebUIUpdateInput,
): CompletableFuture<DataFetcherResult<WebUIUpdatePayload?>> {
return future {
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
withTimeout(30.seconds) {
if (WebInterfaceManager.status.value.state === DOWNLOADING) {
return@withTimeout WebUIUpdatePayload(input.clientMutationId, WebInterfaceManager.status.value)
@@ -59,9 +68,10 @@ class InfoMutation {
}
}
fun resetWebUIUpdateStatus(): CompletableFuture<DataFetcherResult<WebUIUpdateStatus?>> =
fun resetWebUIUpdateStatus(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<DataFetcherResult<WebUIUpdateStatus?>> =
future {
asDataFetcherResult {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
withTimeout(30.seconds) {
val isUpdateFinished = WebInterfaceManager.status.value.state != DOWNLOADING
if (!isUpdateFinished) {

View File

@@ -1,10 +1,15 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.schema.DataFetchingEnvironment
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.KoSyncConnectPayload
import suwayomi.tachidesk.graphql.types.LogoutKoSyncAccountPayload
import suwayomi.tachidesk.graphql.types.SettingsType
import suwayomi.tachidesk.manga.impl.sync.KoreaderSyncService
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import java.util.concurrent.CompletableFuture
class KoreaderSyncMutation {
@@ -14,8 +19,12 @@ class KoreaderSyncMutation {
val password: String,
)
fun connectKoSyncAccount(input: ConnectKoSyncAccountInput): CompletableFuture<KoSyncConnectPayload> =
fun connectKoSyncAccount(
dataFetchingEnvironment: DataFetchingEnvironment,
input: ConnectKoSyncAccountInput,
): CompletableFuture<KoSyncConnectPayload> =
future {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val result = KoreaderSyncService.connect(input.username, input.password)
KoSyncConnectPayload(
@@ -31,8 +40,12 @@ class KoreaderSyncMutation {
val clientMutationId: String? = null,
)
fun logoutKoSyncAccount(input: LogoutKoSyncAccountInput): CompletableFuture<LogoutKoSyncAccountPayload> =
fun logoutKoSyncAccount(
dataFetchingEnvironment: DataFetchingEnvironment,
input: LogoutKoSyncAccountInput,
): CompletableFuture<LogoutKoSyncAccountPayload> =
future {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
KoreaderSyncService.logout()
LogoutKoSyncAccountPayload(
clientMutationId = input.clientMutationId,

View File

@@ -1,6 +1,7 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere
@@ -8,6 +9,7 @@ import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.MangaMetaType
import suwayomi.tachidesk.graphql.types.MangaType
import suwayomi.tachidesk.manga.impl.Library
@@ -16,7 +18,10 @@ import suwayomi.tachidesk.manga.impl.update.IUpdater
import suwayomi.tachidesk.manga.model.table.MangaMetaTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.toDataClass
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import uy.kohesive.injekt.injectLazy
import java.time.Instant
import java.util.concurrent.CompletableFuture
@@ -90,7 +95,11 @@ class MangaMutation {
}
}
fun updateManga(input: UpdateMangaInput): CompletableFuture<DataFetcherResult<UpdateMangaPayload?>> {
fun updateManga(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateMangaInput,
): CompletableFuture<DataFetcherResult<UpdateMangaPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, id, patch) = input
return future {
@@ -110,7 +119,11 @@ class MangaMutation {
}
}
fun updateMangas(input: UpdateMangasInput): CompletableFuture<DataFetcherResult<UpdateMangasPayload?>> {
fun updateMangas(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateMangasInput,
): CompletableFuture<DataFetcherResult<UpdateMangasPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, ids, patch) = input
return future {
@@ -140,7 +153,11 @@ class MangaMutation {
val manga: MangaType,
)
fun fetchManga(input: FetchMangaInput): CompletableFuture<DataFetcherResult<FetchMangaPayload?>> {
fun fetchManga(
dataFetchingEnvironment: DataFetchingEnvironment,
input: FetchMangaInput,
): CompletableFuture<DataFetcherResult<FetchMangaPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, id) = input
return future {
@@ -169,7 +186,11 @@ class MangaMutation {
val meta: MangaMetaType,
)
fun setMangaMeta(input: SetMangaMetaInput): DataFetcherResult<SetMangaMetaPayload?> {
fun setMangaMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: SetMangaMetaInput,
): DataFetcherResult<SetMangaMetaPayload?> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, meta) = input
return asDataFetcherResult {
@@ -191,7 +212,11 @@ class MangaMutation {
val manga: MangaType,
)
fun deleteMangaMeta(input: DeleteMangaMetaInput): DataFetcherResult<DeleteMangaMetaPayload?> {
fun deleteMangaMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DeleteMangaMetaInput,
): DataFetcherResult<DeleteMangaMetaPayload?> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, mangaId, key) = input
return asDataFetcherResult {

View File

@@ -1,6 +1,7 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.selectAll
@@ -8,7 +9,11 @@ import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.global.impl.GlobalMeta
import suwayomi.tachidesk.global.model.table.GlobalMetaTable
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.GlobalMetaType
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
class MetaMutation {
data class SetGlobalMetaInput(
@@ -21,7 +26,11 @@ class MetaMutation {
val meta: GlobalMetaType,
)
fun setGlobalMeta(input: SetGlobalMetaInput): DataFetcherResult<SetGlobalMetaPayload?> {
fun setGlobalMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: SetGlobalMetaInput,
): DataFetcherResult<SetGlobalMetaPayload?> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, meta) = input
return asDataFetcherResult {
@@ -41,7 +50,11 @@ class MetaMutation {
val meta: GlobalMetaType?,
)
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DataFetcherResult<DeleteGlobalMetaPayload?> {
fun deleteGlobalMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DeleteGlobalMetaInput,
): DataFetcherResult<DeleteGlobalMetaPayload?> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, key) = input
return asDataFetcherResult {

View File

@@ -1,14 +1,19 @@
package suwayomi.tachidesk.graphql.mutations
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
import graphql.schema.DataFetchingEnvironment
import kotlinx.coroutines.flow.MutableStateFlow
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.PartialSettingsType
import suwayomi.tachidesk.graphql.types.Settings
import suwayomi.tachidesk.graphql.types.SettingsType
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.repoMatchRegex
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.SERVER_CONFIG_MODULE_NAME
import suwayomi.tachidesk.server.ServerConfig
import suwayomi.tachidesk.server.serverConfig
import suwayomi.tachidesk.server.user.requireUser
import xyz.nulldev.ts.config.GlobalConfigManager
import java.io.File
@@ -226,7 +231,11 @@ class SettingsMutation {
updateSetting(settings.koreaderSyncPercentageTolerance, serverConfig.koreaderSyncPercentageTolerance)
}
fun setSettings(input: SetSettingsInput): SetSettingsPayload {
fun setSettings(
dataFetchingEnvironment: DataFetchingEnvironment,
input: SetSettingsInput,
): SetSettingsPayload {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, settings) = input
validateSettings(settings)
@@ -244,7 +253,11 @@ class SettingsMutation {
val settings: SettingsType,
)
fun resetSettings(input: ResetSettingsInput): ResetSettingsPayload {
fun resetSettings(
dataFetchingEnvironment: DataFetchingEnvironment,
input: ResetSettingsInput,
): ResetSettingsPayload {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId) = input
GlobalConfigManager.resetUserConfig()

View File

@@ -6,12 +6,14 @@ import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.SwitchPreferenceCompat
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.FilterChange
import suwayomi.tachidesk.graphql.types.MangaType
import suwayomi.tachidesk.graphql.types.Preference
@@ -25,7 +27,10 @@ import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
import suwayomi.tachidesk.manga.model.table.SourceTable
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import java.util.concurrent.CompletableFuture
class SourceMutation {
@@ -39,7 +44,11 @@ class SourceMutation {
val meta: SourceMetaType,
)
fun setSourceMeta(input: SetSourceMetaInput): DataFetcherResult<SetSourceMetaPayload?> {
fun setSourceMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: SetSourceMetaInput,
): DataFetcherResult<SetSourceMetaPayload?> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, meta) = input
return asDataFetcherResult {
@@ -61,7 +70,11 @@ class SourceMutation {
val source: SourceType?,
)
fun deleteSourceMeta(input: DeleteSourceMetaInput): DataFetcherResult<DeleteSourceMetaPayload?> {
fun deleteSourceMeta(
dataFetchingEnvironment: DataFetchingEnvironment,
input: DeleteSourceMetaInput,
): DataFetcherResult<DeleteSourceMetaPayload?> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, sourceId, key) = input
return asDataFetcherResult {
@@ -116,7 +129,11 @@ class SourceMutation {
val hasNextPage: Boolean,
)
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<DataFetcherResult<FetchSourceMangaPayload?>> {
fun fetchSourceManga(
dataFetchingEnvironment: DataFetchingEnvironment,
input: FetchSourceMangaInput,
): CompletableFuture<DataFetcherResult<FetchSourceMangaPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, sourceId, type, page, query, filters) = input
return future {
@@ -182,7 +199,11 @@ class SourceMutation {
val source: SourceType,
)
fun updateSourcePreference(input: UpdateSourcePreferenceInput): DataFetcherResult<UpdateSourcePreferencePayload?> {
fun updateSourcePreference(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateSourcePreferenceInput,
): DataFetcherResult<UpdateSourcePreferencePayload?> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, sourceId, change) = input
return asDataFetcherResult {

View File

@@ -3,16 +3,21 @@ package suwayomi.tachidesk.graphql.mutations
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.TrackRecordType
import suwayomi.tachidesk.graphql.types.TrackerType
import suwayomi.tachidesk.manga.impl.track.Track
import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
import suwayomi.tachidesk.manga.model.table.TrackRecordTable
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import java.util.concurrent.CompletableFuture
class TrackMutation {
@@ -28,7 +33,11 @@ class TrackMutation {
val tracker: TrackerType,
)
fun loginTrackerOAuth(input: LoginTrackerOAuthInput): CompletableFuture<LoginTrackerOAuthPayload> {
fun loginTrackerOAuth(
dataFetchingEnvironment: DataFetchingEnvironment,
input: LoginTrackerOAuthInput,
): CompletableFuture<LoginTrackerOAuthPayload> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val tracker =
requireNotNull(TrackerManager.getTracker(input.trackerId)) {
"Could not find tracker"
@@ -57,7 +66,11 @@ class TrackMutation {
val tracker: TrackerType,
)
fun loginTrackerCredentials(input: LoginTrackerCredentialsInput): CompletableFuture<LoginTrackerCredentialsPayload> {
fun loginTrackerCredentials(
dataFetchingEnvironment: DataFetchingEnvironment,
input: LoginTrackerCredentialsInput,
): CompletableFuture<LoginTrackerCredentialsPayload> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val tracker =
requireNotNull(TrackerManager.getTracker(input.trackerId)) {
"Could not find tracker"
@@ -84,7 +97,11 @@ class TrackMutation {
val tracker: TrackerType,
)
fun logoutTracker(input: LogoutTrackerInput): CompletableFuture<LogoutTrackerPayload> {
fun logoutTracker(
dataFetchingEnvironment: DataFetchingEnvironment,
input: LogoutTrackerInput,
): CompletableFuture<LogoutTrackerPayload> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val tracker =
requireNotNull(TrackerManager.getTracker(input.trackerId)) {
"Could not find tracker"
@@ -117,7 +134,11 @@ class TrackMutation {
val trackRecord: TrackRecordType,
)
fun bindTrack(input: BindTrackInput): CompletableFuture<BindTrackPayload> {
fun bindTrack(
dataFetchingEnvironment: DataFetchingEnvironment,
input: BindTrackInput,
): CompletableFuture<BindTrackPayload> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, mangaId, trackerId, remoteId, private) = input
return future {
@@ -152,7 +173,11 @@ class TrackMutation {
val trackRecord: TrackRecordType,
)
fun fetchTrack(input: FetchTrackInput): CompletableFuture<FetchTrackPayload> {
fun fetchTrack(
dataFetchingEnvironment: DataFetchingEnvironment,
input: FetchTrackInput,
): CompletableFuture<FetchTrackPayload> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, recordId) = input
return future {
@@ -184,7 +209,11 @@ class TrackMutation {
val trackRecord: TrackRecordType?,
)
fun unbindTrack(input: UnbindTrackInput): CompletableFuture<UnbindTrackPayload> {
fun unbindTrack(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UnbindTrackInput,
): CompletableFuture<UnbindTrackPayload> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, recordId, deleteRemoteTrack) = input
return future {
@@ -214,7 +243,11 @@ class TrackMutation {
val trackRecords: List<TrackRecordType>,
)
fun trackProgress(input: TrackProgressInput): CompletableFuture<DataFetcherResult<TrackProgressPayload?>> {
fun trackProgress(
dataFetchingEnvironment: DataFetchingEnvironment,
input: TrackProgressInput,
): CompletableFuture<DataFetcherResult<TrackProgressPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
val (clientMutationId, mangaId) = input
return future {
@@ -256,8 +289,12 @@ class TrackMutation {
val trackRecord: TrackRecordType?,
)
fun updateTrack(input: UpdateTrackInput): CompletableFuture<UpdateTrackPayload> =
fun updateTrack(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateTrackInput,
): CompletableFuture<UpdateTrackPayload> =
future {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
Track.update(
Track.UpdateInput(
input.recordId,

View File

@@ -1,14 +1,19 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import graphql.schema.DataFetchingEnvironment
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.graphql.types.LibraryUpdateStatus
import suwayomi.tachidesk.graphql.types.UpdateStatus
import suwayomi.tachidesk.manga.impl.Category
import suwayomi.tachidesk.manga.impl.update.IUpdater
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.CompletableFuture
import kotlin.time.Duration.Companion.seconds
@@ -26,7 +31,11 @@ class UpdateMutation {
val updateStatus: LibraryUpdateStatus,
)
fun updateLibrary(input: UpdateLibraryInput): CompletableFuture<DataFetcherResult<UpdateLibraryPayload?>> {
fun updateLibrary(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateLibraryInput,
): CompletableFuture<DataFetcherResult<UpdateLibraryPayload?>> {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
updater.addCategoriesToUpdateQueue(
Category.getCategoryList().filter { input.categories?.contains(it.id) ?: true },
clear = true,
@@ -57,8 +66,12 @@ class UpdateMutation {
val updateStatus: UpdateStatus,
)
fun updateLibraryManga(input: UpdateLibraryMangaInput): CompletableFuture<DataFetcherResult<UpdateLibraryMangaPayload?>> {
fun updateLibraryManga(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateLibraryMangaInput,
): CompletableFuture<DataFetcherResult<UpdateLibraryMangaPayload?>> {
updateLibrary(
dataFetchingEnvironment,
UpdateLibraryInput(
clientMutationId = input.clientMutationId,
categories = null,
@@ -88,8 +101,12 @@ class UpdateMutation {
val updateStatus: UpdateStatus,
)
fun updateCategoryManga(input: UpdateCategoryMangaInput): CompletableFuture<DataFetcherResult<UpdateCategoryMangaPayload?>> {
fun updateCategoryManga(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateCategoryMangaInput,
): CompletableFuture<DataFetcherResult<UpdateCategoryMangaPayload?>> {
updateLibrary(
dataFetchingEnvironment,
UpdateLibraryInput(
clientMutationId = input.clientMutationId,
categories = input.categories,
@@ -117,7 +134,11 @@ class UpdateMutation {
val clientMutationId: String?,
)
fun updateStop(input: UpdateStopInput): UpdateStopPayload {
fun updateStop(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateStopInput,
): UpdateStopPayload {
dataFetchingEnvironment.getAttribute(Attribute.TachideskUser).requireUser()
updater.reset()
return UpdateStopPayload(input.clientMutationId)
}

View File

@@ -0,0 +1,64 @@
package suwayomi.tachidesk.graphql.mutations
import graphql.schema.DataFetchingEnvironment
import suwayomi.tachidesk.global.impl.util.Jwt
import suwayomi.tachidesk.graphql.server.getAttribute
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.serverConfig
import suwayomi.tachidesk.server.user.UserType
class UserMutation {
data class LoginInput(
val clientMutationId: String? = null,
val username: String,
val password: String,
)
data class LoginPayload(
val clientMutationId: String?,
val accessToken: String,
val refreshToken: String,
)
fun login(
dataFetchingEnvironment: DataFetchingEnvironment,
input: LoginInput,
): LoginPayload {
if (dataFetchingEnvironment.getAttribute(Attribute.TachideskUser) !is UserType.Visitor) {
throw IllegalArgumentException("Cannot login while already logged-in")
}
val isValid =
input.username == serverConfig.authUsername.value &&
input.password == serverConfig.authPassword.value
if (isValid) {
val jwt = Jwt.generateJwt()
return LoginPayload(
clientMutationId = input.clientMutationId,
accessToken = jwt.accessToken,
refreshToken = jwt.refreshToken,
)
} else {
throw Exception("Incorrect username or password.")
}
}
data class RefreshTokenInput(
val clientMutationId: String? = null,
val refreshToken: String,
)
data class RefreshTokenPayload(
val clientMutationId: String?,
val accessToken: String,
)
fun refreshToken(input: RefreshTokenInput): RefreshTokenPayload {
val accessToken = Jwt.refreshJwt(input.refreshToken)
return RefreshTokenPayload(
clientMutationId = input.clientMutationId,
accessToken = accessToken,
)
}
}