Feature/graphql settings add jwt settings (#1612)
* Add jwt settings to grapqhl SettingsType * Sort proto BackupServerSettings by ProtNumber
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+122
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
+9
-18
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user