Feature/graphql settings add jwt settings (#1612)

* Add jwt settings to grapqhl SettingsType

* Sort proto BackupServerSettings by ProtNumber
This commit is contained in:
schroda
2025-08-24 18:35:59 +02:00
committed by GitHub
parent 8ae451ece5
commit 9a33e3808a
6 changed files with 153 additions and 18 deletions

View File

@@ -182,6 +182,9 @@ class SettingsMutation {
// Authentication
updateSetting(settings.authMode, serverConfig.authMode)
updateSetting(settings.jwtAudience, serverConfig.jwtAudience)
updateSetting(settings.jwtTokenExpiry, serverConfig.jwtTokenExpiry)
updateSetting(settings.jwtRefreshExpiry, serverConfig.jwtRefreshExpiry)
updateSetting(settings.authUsername, serverConfig.authUsername)
updateSetting(settings.authPassword, serverConfig.authPassword)
updateSetting(settings.basicAuthEnabled, serverConfig.basicAuthEnabled)

View File

@@ -43,6 +43,7 @@ import suwayomi.tachidesk.graphql.queries.TrackQuery
import suwayomi.tachidesk.graphql.queries.UpdateQuery
import suwayomi.tachidesk.graphql.server.primitives.Cursor
import suwayomi.tachidesk.graphql.server.primitives.GraphQLCursor
import suwayomi.tachidesk.graphql.server.primitives.GraphQLDurationAsString
import suwayomi.tachidesk.graphql.server.primitives.GraphQLLongAsString
import suwayomi.tachidesk.graphql.server.primitives.GraphQLUpload
import suwayomi.tachidesk.graphql.subscriptions.DownloadSubscription
@@ -50,11 +51,13 @@ import suwayomi.tachidesk.graphql.subscriptions.InfoSubscription
import suwayomi.tachidesk.graphql.subscriptions.UpdateSubscription
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.time.Duration
class CustomSchemaGeneratorHooks : FlowSubscriptionSchemaGeneratorHooks() {
override fun willGenerateGraphQLType(type: KType): GraphQLType? =
when (type.classifier as? KClass<*>) {
Long::class -> GraphQLLongAsString // encode to string for JS
Duration::class -> GraphQLDurationAsString // encode Duration as ISO-8601 string
Cursor::class -> GraphQLCursor
UploadedFile::class -> GraphQLUpload
else -> super.willGenerateGraphQLType(type)

View File

@@ -0,0 +1,122 @@
package suwayomi.tachidesk.graphql.server.primitives
import graphql.GraphQLContext
import graphql.execution.CoercedVariables
import graphql.language.StringValue
import graphql.language.Value
import graphql.scalar.CoercingUtil
import graphql.schema.Coercing
import graphql.schema.CoercingParseLiteralException
import graphql.schema.CoercingParseValueException
import graphql.schema.CoercingSerializeException
import graphql.schema.GraphQLScalarType
import java.util.Locale
import kotlin.time.Duration
val GraphQLDurationAsString: GraphQLScalarType =
GraphQLScalarType
.newScalar()
.name("Duration")
.description("An ISO-8601 encoded duration string")
.coercing(GraphqlDurationAsStringCoercing())
.build()
private class GraphqlDurationAsStringCoercing : Coercing<Duration, String> {
private fun toStringImpl(input: Any): String =
when (input) {
is Duration -> input.toIsoString()
is String -> Duration.parse(input).toIsoString()
else -> throw CoercingSerializeException(
"Expected a Duration or String but was ${CoercingUtil.typeName(input)}",
)
}
private fun parseValueImpl(
input: Any,
locale: Locale,
): Duration {
if (input !is String) {
throw CoercingParseValueException(
CoercingUtil.i18nMsg(
locale,
"String.unexpectedRawValueType",
CoercingUtil.typeName(input),
),
)
}
return try {
Duration.parse(input)
} catch (e: IllegalArgumentException) {
throw CoercingParseValueException(
"Invalid duration format: $input. Expected ISO-8601 duration string (e.g., 'PT30M', 'P1D')",
e,
)
}
}
private fun parseLiteralImpl(
input: Any,
locale: Locale,
): Duration {
if (input !is StringValue) {
throw CoercingParseLiteralException(
CoercingUtil.i18nMsg(
locale,
"Scalar.unexpectedAstType",
"StringValue",
CoercingUtil.typeName(input),
),
)
}
return try {
Duration.parse(input.value)
} catch (e: IllegalArgumentException) {
throw CoercingParseLiteralException(
"Invalid duration format: ${input.value}. Expected ISO-8601 duration string (e.g., 'PT30M', 'P1D')",
e,
)
}
}
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(toStringImpl(input)).build()
@Deprecated("")
override fun serialize(dataFetcherResult: Any): String = toStringImpl(dataFetcherResult)
@Throws(CoercingSerializeException::class)
override fun serialize(
dataFetcherResult: Any,
graphQLContext: GraphQLContext,
locale: Locale,
): String = toStringImpl(dataFetcherResult)
@Deprecated("")
override fun parseValue(input: Any): Duration = parseValueImpl(input, Locale.getDefault())
@Throws(CoercingParseValueException::class)
override fun parseValue(
input: Any,
graphQLContext: GraphQLContext,
locale: Locale,
): Duration = parseValueImpl(input, locale)
@Deprecated("")
override fun parseLiteral(input: Any): Duration = parseLiteralImpl(input, Locale.getDefault())
@Throws(CoercingParseLiteralException::class)
override fun parseLiteral(
input: Value<*>,
variables: CoercedVariables,
graphQLContext: GraphQLContext,
locale: Locale,
): Duration = parseLiteralImpl(input, locale)
@Deprecated("")
override fun valueToLiteral(input: Any): Value<*> = valueToLiteralImpl(input)
override fun valueToLiteral(
input: Any,
graphQLContext: GraphQLContext,
locale: Locale,
): Value<*> = valueToLiteralImpl(input)
}

View File

@@ -12,6 +12,7 @@ import org.jetbrains.exposed.sql.SortOrder
import suwayomi.tachidesk.graphql.server.primitives.Node
import suwayomi.tachidesk.server.ServerConfig
import suwayomi.tachidesk.server.serverConfig
import kotlin.time.Duration
interface Settings : Node {
val ip: String?
@@ -65,6 +66,9 @@ interface Settings : Node {
// Authentication
val authMode: AuthMode?
val jwtAudience: String?
val jwtTokenExpiry: Duration?
val jwtRefreshExpiry: Duration?
val authUsername: String?
val authPassword: String?
@@ -177,6 +181,9 @@ data class PartialSettingsType(
override val updateMangas: Boolean?,
// Authentication
override val authMode: AuthMode?,
override val jwtAudience: String?,
override val jwtTokenExpiry: Duration?,
override val jwtRefreshExpiry: Duration?,
override val authUsername: String?,
override val authPassword: String?,
@GraphQLDeprecated("Removed - prefer authMode")
@@ -267,6 +274,9 @@ class SettingsType(
override val updateMangas: Boolean,
// Authentication
override val authMode: AuthMode,
override val jwtAudience: String,
override val jwtTokenExpiry: Duration,
override val jwtRefreshExpiry: Duration,
override val authUsername: String,
override val authPassword: String,
@GraphQLDeprecated("Removed - prefer authMode")
@@ -358,6 +368,9 @@ class SettingsType(
config.updateMangas.value,
// Authentication
config.authMode.value,
config.jwtAudience.value,
config.jwtTokenExpiry.value,
config.jwtRefreshExpiry.value,
config.authUsername.value,
config.authPassword.value,
config.basicAuthEnabled.value,

View File

@@ -429,6 +429,9 @@ object ProtoBackupExport : ProtoBackupBase() {
updateMangas = serverConfig.updateMangas.value,
// Authentication
authMode = serverConfig.authMode.value,
jwtAudience = serverConfig.jwtAudience.value,
jwtTokenExpiry = serverConfig.jwtTokenExpiry.value,
jwtRefreshExpiry = serverConfig.jwtRefreshExpiry.value,
authUsername = serverConfig.authUsername.value,
authPassword = serverConfig.authPassword.value,
basicAuthEnabled = false,

View File

@@ -11,26 +11,24 @@ import suwayomi.tachidesk.graphql.types.SettingsDownloadConversion
import suwayomi.tachidesk.graphql.types.WebUIChannel
import suwayomi.tachidesk.graphql.types.WebUIFlavor
import suwayomi.tachidesk.graphql.types.WebUIInterface
import kotlin.time.Duration
@Serializable
data class BackupServerSettings(
@ProtoNumber(1) override var ip: String,
@ProtoNumber(2) override var port: Int,
// socks
@ProtoNumber(3) override var socksProxyEnabled: Boolean,
@ProtoNumber(4) override var socksProxyVersion: Int,
@ProtoNumber(5) override var socksProxyHost: String,
@ProtoNumber(6) override var socksProxyPort: String,
@ProtoNumber(7) override var socksProxyUsername: String,
@ProtoNumber(8) override var socksProxyPassword: String,
// webUI
@ProtoNumber(9) override var webUIFlavor: WebUIFlavor,
@ProtoNumber(10) override var initialOpenInBrowserEnabled: Boolean,
@ProtoNumber(11) override var webUIInterface: WebUIInterface,
@ProtoNumber(12) override var electronPath: String,
@ProtoNumber(13) override var webUIChannel: WebUIChannel,
@ProtoNumber(14) override var webUIUpdateCheckInterval: Double,
// downloader
@ProtoNumber(15) override var downloadAsCbz: Boolean,
@ProtoNumber(16) override var downloadsPath: String,
@ProtoNumber(17) override var autoDownloadNewChapters: Boolean,
@@ -38,47 +36,33 @@ data class BackupServerSettings(
@ProtoNumber(19) override var autoDownloadAheadLimit: Int,
@ProtoNumber(20) override var autoDownloadNewChaptersLimit: Int,
@ProtoNumber(21) override var autoDownloadIgnoreReUploads: Boolean,
@ProtoNumber(57) override val downloadConversions: List<BackupSettingsDownloadConversionType>?,
// extension
@ProtoNumber(22) override var extensionRepos: List<String>,
// requests
@ProtoNumber(23) override var maxSourcesInParallel: Int,
// updater
@ProtoNumber(24) override var excludeUnreadChapters: Boolean,
@ProtoNumber(25) override var excludeNotStarted: Boolean,
@ProtoNumber(26) override var excludeCompleted: Boolean,
@ProtoNumber(27) override var globalUpdateInterval: Double,
@ProtoNumber(28) override var updateMangas: Boolean,
// Authentication
@ProtoNumber(56) override var authMode: AuthMode,
@ProtoNumber(29) override var basicAuthEnabled: Boolean?,
@ProtoNumber(30) override var authUsername: String,
@ProtoNumber(31) override var authPassword: String,
// deprecated
@ProtoNumber(99991) override var basicAuthUsername: String?,
@ProtoNumber(99992) override var basicAuthPassword: String?,
// misc
@ProtoNumber(32) override var debugLogsEnabled: Boolean,
@ProtoNumber(33) override var gqlDebugLogsEnabled: Boolean,
@ProtoNumber(34) override var systemTrayEnabled: Boolean,
@ProtoNumber(35) override var maxLogFiles: Int,
@ProtoNumber(36) override var maxLogFileSize: String,
@ProtoNumber(37) override var maxLogFolderSize: String,
// backup
@ProtoNumber(38) override var backupPath: String,
@ProtoNumber(39) override var backupTime: String,
@ProtoNumber(40) override var backupInterval: Int,
@ProtoNumber(41) override var backupTTL: Int,
// local source
@ProtoNumber(42) override var localSourcePath: String,
// cloudflare bypass
@ProtoNumber(43) override var flareSolverrEnabled: Boolean,
@ProtoNumber(44) override var flareSolverrUrl: String,
@ProtoNumber(45) override var flareSolverrTimeout: Int,
@ProtoNumber(46) override var flareSolverrSessionName: String,
@ProtoNumber(47) override var flareSolverrSessionTtl: Int,
@ProtoNumber(48) override var flareSolverrAsResponseFallback: Boolean,
// opds
@ProtoNumber(49) override var opdsUseBinaryFileSizes: Boolean,
@ProtoNumber(50) override var opdsItemsPerPage: Int,
@ProtoNumber(51) override var opdsEnablePageReadProgress: Boolean,
@@ -86,7 +70,9 @@ data class BackupServerSettings(
@ProtoNumber(53) override var opdsShowOnlyUnreadChapters: Boolean,
@ProtoNumber(54) override var opdsShowOnlyDownloadedChapters: Boolean,
@ProtoNumber(55) override var opdsChapterSortOrder: SortOrder,
// koreader sync
@ProtoNumber(56) override var authMode: AuthMode,
@ProtoNumber(57) override val downloadConversions: List<BackupSettingsDownloadConversionType>?,
@ProtoNumber(58) override var jwtAudience: String?,
@ProtoNumber(59) override var koreaderSyncServerUrl: String,
@ProtoNumber(60) override var koreaderSyncUsername: String,
@ProtoNumber(61) override var koreaderSyncUserkey: String,
@@ -94,6 +80,11 @@ data class BackupServerSettings(
@ProtoNumber(63) override var koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod,
@ProtoNumber(64) override var koreaderSyncStrategy: KoreaderSyncStrategy,
@ProtoNumber(65) override var koreaderSyncPercentageTolerance: Double,
@ProtoNumber(66) override var jwtTokenExpiry: Duration?,
@ProtoNumber(67) override var jwtRefreshExpiry: Duration?,
// Deprecated settings
@ProtoNumber(99991) override var basicAuthUsername: String?,
@ProtoNumber(99992) override var basicAuthPassword: String?,
) : Settings {
@Serializable
class BackupSettingsDownloadConversionType(