mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-11-26 20:18:48 +00:00
commit
96311ee43d
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,5 +1,19 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.13.0
|
||||||
|
|
||||||
|
**ALL DEPRECATIONS HAVE BEEN REMOVED**
|
||||||
|
**A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED**
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Exposed`:
|
||||||
|
* `AbstractExposedWriteCRUDRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `createAndInsertId`
|
||||||
|
* Old `update` method has been deprecated and not recommended to override anymore in realizations
|
||||||
|
* Old `insert` method now is `open` instead of `abstract` and can be omitted
|
||||||
|
* `AbstractExposedKeyValueRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `insertKey`
|
||||||
|
* Old `update` method has been deprecated and not recommended to override anymore
|
||||||
|
* Old `insert` method now is `open` instead of `abstract` and can be omitted in realizations
|
||||||
|
|
||||||
## 0.12.17
|
## 0.12.17
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
|
@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.12.17
|
version=0.13.0
|
||||||
android_code_version=156
|
android_code_version=158
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.ktor.client
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
|
||||||
import dev.inmo.micro_utils.coroutines.safely
|
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
|
||||||
import io.ktor.client.HttpClient
|
|
||||||
import io.ktor.client.plugins.pluginOrNull
|
|
||||||
import io.ktor.client.plugins.websocket.WebSockets
|
|
||||||
import io.ktor.client.plugins.websocket.ws
|
|
||||||
import io.ktor.client.request.HttpRequestBuilder
|
|
||||||
import io.ktor.websocket.Frame
|
|
||||||
import io.ktor.websocket.readBytes
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.serialization.DeserializationStrategy
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
|
||||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
|
||||||
*/
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
|
||||||
url: String,
|
|
||||||
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
|
||||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
|
|
||||||
): Flow<T> {
|
|
||||||
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
|
||||||
|
|
||||||
val correctedUrl = url.asCorrectWebSocketUrl
|
|
||||||
|
|
||||||
return channelFlow {
|
|
||||||
do {
|
|
||||||
val reconnect = runCatchingSafely {
|
|
||||||
ws(correctedUrl, requestBuilder) {
|
|
||||||
for (received in incoming) {
|
|
||||||
when (received) {
|
|
||||||
is Frame.Binary -> send(conversation(received.data))
|
|
||||||
else -> {
|
|
||||||
close()
|
|
||||||
return@ws
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkReconnection(null)
|
|
||||||
}.getOrElse { e ->
|
|
||||||
checkReconnection(e).also {
|
|
||||||
if (!it) {
|
|
||||||
close(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (reconnect && isActive)
|
|
||||||
|
|
||||||
if (isActive) {
|
|
||||||
safely {
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
|
||||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
|
||||||
*/
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
|
||||||
url: String,
|
|
||||||
deserializer: DeserializationStrategy<T>,
|
|
||||||
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
) = createStandardWebsocketFlow(
|
|
||||||
url,
|
|
||||||
checkReconnection,
|
|
||||||
requestBuilder
|
|
||||||
) {
|
|
||||||
serialFormat.decodeDefault(deserializer, it)
|
|
||||||
}
|
|
@ -1,260 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.ktor.client
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.common.MPPFile
|
|
||||||
import dev.inmo.micro_utils.common.filename
|
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
|
||||||
import io.ktor.client.HttpClient
|
|
||||||
import io.ktor.client.call.body
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.request.forms.*
|
|
||||||
import io.ktor.client.statement.readBytes
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.utils.io.core.ByteReadPacket
|
|
||||||
import kotlinx.serialization.*
|
|
||||||
|
|
||||||
@Deprecated("This class will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
class UnifiedRequester(
|
|
||||||
val client: HttpClient = HttpClient(),
|
|
||||||
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
|
||||||
) {
|
|
||||||
suspend fun <ResultType> uniget(
|
|
||||||
url: String,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>
|
|
||||||
): ResultType = client.uniget(url, resultDeserializer, serialFormat)
|
|
||||||
|
|
||||||
fun <T> encodeUrlQueryValue(
|
|
||||||
serializationStrategy: SerializationStrategy<T>,
|
|
||||||
value: T
|
|
||||||
) = serializationStrategy.encodeUrlQueryValue(
|
|
||||||
value,
|
|
||||||
serialFormat
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun <BodyType, ResultType> unipost(
|
|
||||||
url: String,
|
|
||||||
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>
|
|
||||||
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
|
|
||||||
|
|
||||||
suspend fun <ResultType> unimultipart(
|
|
||||||
url: String,
|
|
||||||
filename: String,
|
|
||||||
inputProvider: InputProvider,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
mimetype: String = "*/*",
|
|
||||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
|
||||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
|
||||||
|
|
||||||
suspend fun <BodyType, ResultType> unimultipart(
|
|
||||||
url: String,
|
|
||||||
filename: String,
|
|
||||||
inputProvider: InputProvider,
|
|
||||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
mimetype: String = "*/*",
|
|
||||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
|
||||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
|
||||||
|
|
||||||
suspend fun <ResultType> unimultipart(
|
|
||||||
url: String,
|
|
||||||
mppFile: MPPFile,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
mimetype: String = "*/*",
|
|
||||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
|
||||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
|
||||||
): ResultType = client.unimultipart(
|
|
||||||
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun <BodyType, ResultType> unimultipart(
|
|
||||||
url: String,
|
|
||||||
mppFile: MPPFile,
|
|
||||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
mimetype: String = "*/*",
|
|
||||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
|
||||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
|
||||||
): ResultType = client.unimultipart(
|
|
||||||
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
|
||||||
)
|
|
||||||
|
|
||||||
fun <T> createStandardWebsocketFlow(
|
|
||||||
url: String,
|
|
||||||
checkReconnection: suspend (Throwable?) -> Boolean,
|
|
||||||
deserializer: DeserializationStrategy<T>,
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
|
|
||||||
|
|
||||||
fun <T> createStandardWebsocketFlow(
|
|
||||||
url: String,
|
|
||||||
deserializer: DeserializationStrategy<T>,
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("This property will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
val defaultRequester = UnifiedRequester()
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <ResultType> HttpClient.uniget(
|
|
||||||
url: String,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
|
||||||
) = get(url).let {
|
|
||||||
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
|
|
||||||
value: T,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
|
||||||
) = serialFormat.encodeHex(
|
|
||||||
this,
|
|
||||||
value
|
|
||||||
)
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <BodyType, ResultType> HttpClient.unipost(
|
|
||||||
url: String,
|
|
||||||
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
|
||||||
) = post(url) {
|
|
||||||
setBody(
|
|
||||||
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
|
||||||
)
|
|
||||||
}.let {
|
|
||||||
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <ResultType> HttpClient.unimultipart(
|
|
||||||
url: String,
|
|
||||||
filename: String,
|
|
||||||
inputProvider: InputProvider,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
mimetype: String = "*/*",
|
|
||||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
|
||||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
|
||||||
): ResultType = submitFormWithBinaryData(
|
|
||||||
url,
|
|
||||||
formData = formData {
|
|
||||||
append(
|
|
||||||
"bytes",
|
|
||||||
inputProvider,
|
|
||||||
Headers.build {
|
|
||||||
append(HttpHeaders.ContentType, mimetype)
|
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
|
|
||||||
dataHeadersBuilder()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
additionalParametersBuilder()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
requestBuilder()
|
|
||||||
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
|
||||||
url: String,
|
|
||||||
filename: String,
|
|
||||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
|
||||||
inputProvider: InputProvider,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
mimetype: String = "*/*",
|
|
||||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
|
||||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
|
||||||
): ResultType = unimultipart(
|
|
||||||
url,
|
|
||||||
filename,
|
|
||||||
inputProvider,
|
|
||||||
resultDeserializer,
|
|
||||||
mimetype,
|
|
||||||
additionalParametersBuilder = {
|
|
||||||
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
|
||||||
append(
|
|
||||||
"data",
|
|
||||||
InputProvider(serialized.size.toLong()) {
|
|
||||||
ByteReadPacket(serialized)
|
|
||||||
},
|
|
||||||
Headers.build {
|
|
||||||
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
|
||||||
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
|
||||||
dataHeadersBuilder()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
additionalParametersBuilder()
|
|
||||||
},
|
|
||||||
dataHeadersBuilder,
|
|
||||||
requestBuilder,
|
|
||||||
serialFormat
|
|
||||||
)
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <ResultType> HttpClient.unimultipart(
|
|
||||||
url: String,
|
|
||||||
mppFile: MPPFile,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
mimetype: String = "*/*",
|
|
||||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
|
||||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
|
||||||
): ResultType = unimultipart(
|
|
||||||
url,
|
|
||||||
mppFile.filename.string,
|
|
||||||
mppFile.inputProvider(),
|
|
||||||
resultDeserializer,
|
|
||||||
mimetype,
|
|
||||||
additionalParametersBuilder,
|
|
||||||
dataHeadersBuilder,
|
|
||||||
requestBuilder,
|
|
||||||
serialFormat
|
|
||||||
)
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
|
||||||
url: String,
|
|
||||||
mppFile: MPPFile,
|
|
||||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
|
||||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
|
||||||
mimetype: String = "*/*",
|
|
||||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
|
||||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
|
||||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
|
||||||
): ResultType = unimultipart(
|
|
||||||
url,
|
|
||||||
mppFile,
|
|
||||||
resultDeserializer,
|
|
||||||
mimetype,
|
|
||||||
additionalParametersBuilder = {
|
|
||||||
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
|
||||||
append(
|
|
||||||
"data",
|
|
||||||
InputProvider(serialized.size.toLong()) {
|
|
||||||
ByteReadPacket(serialized)
|
|
||||||
},
|
|
||||||
Headers.build {
|
|
||||||
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
|
||||||
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
|
||||||
dataHeadersBuilder()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
additionalParametersBuilder()
|
|
||||||
},
|
|
||||||
dataHeadersBuilder,
|
|
||||||
requestBuilder,
|
|
||||||
serialFormat
|
|
||||||
)
|
|
@ -9,11 +9,3 @@ expect suspend fun HttpClient.tempUpload(
|
|||||||
file: MPPFile,
|
file: MPPFile,
|
||||||
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
|
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
|
||||||
): TemporalFileId
|
): TemporalFileId
|
||||||
|
|
||||||
suspend fun UnifiedRequester.tempUpload(
|
|
||||||
fullTempUploadDraftPath: String,
|
|
||||||
file: MPPFile,
|
|
||||||
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
|
|
||||||
): TemporalFileId = client.tempUpload(
|
|
||||||
fullTempUploadDraftPath, file, onUpload
|
|
||||||
)
|
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.ktor.server
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.safely
|
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
|
||||||
import io.ktor.http.URLProtocol
|
|
||||||
import io.ktor.server.application.install
|
|
||||||
import io.ktor.server.application.pluginOrNull
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.application
|
|
||||||
import io.ktor.server.websocket.*
|
|
||||||
import io.ktor.websocket.send
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.serialization.SerializationStrategy
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon")
|
|
||||||
fun <T> Route.includeWebsocketHandling(
|
|
||||||
suburl: String,
|
|
||||||
flow: Flow<T>,
|
|
||||||
protocol: URLProtocol? = null,
|
|
||||||
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
|
|
||||||
) {
|
|
||||||
application.apply {
|
|
||||||
pluginOrNull(WebSockets) ?: install(WebSockets)
|
|
||||||
}
|
|
||||||
webSocket(suburl, protocol ?.name) {
|
|
||||||
safely {
|
|
||||||
flow.collect {
|
|
||||||
converter(it) ?.let { data ->
|
|
||||||
send(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon")
|
|
||||||
fun <T> Route.includeWebsocketHandling(
|
|
||||||
suburl: String,
|
|
||||||
flow: Flow<T>,
|
|
||||||
serializer: SerializationStrategy<T>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
protocol: URLProtocol? = null,
|
|
||||||
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
|
||||||
) = includeWebsocketHandling(
|
|
||||||
suburl,
|
|
||||||
flow,
|
|
||||||
protocol,
|
|
||||||
converter = if (filter == null) {
|
|
||||||
{
|
|
||||||
serialFormat.encodeDefault(serializer, it)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
{
|
|
||||||
if (filter(it)) {
|
|
||||||
serialFormat.encodeDefault(serializer, it)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
@ -19,106 +19,6 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.serialization.DeserializationStrategy
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
import kotlinx.serialization.SerializationStrategy
|
import kotlinx.serialization.SerializationStrategy
|
||||||
|
|
||||||
@Deprecated("This class method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
class UnifiedRouter(
|
|
||||||
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) {
|
|
||||||
fun <T> Route.includeWebsocketHandling(
|
|
||||||
suburl: String,
|
|
||||||
flow: Flow<T>,
|
|
||||||
serializer: SerializationStrategy<T>,
|
|
||||||
protocol: URLProtocol? = null,
|
|
||||||
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
|
||||||
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
|
|
||||||
|
|
||||||
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
|
|
||||||
answerSerializer: SerializationStrategy<T>,
|
|
||||||
answer: T
|
|
||||||
) {
|
|
||||||
call.respondBytes (
|
|
||||||
serialFormat.encodeDefault(answerSerializer, answer),
|
|
||||||
serialFormatContentType
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
|
|
||||||
deserializer: DeserializationStrategy<T>
|
|
||||||
) = safely {
|
|
||||||
serialFormat.decodeDefault(
|
|
||||||
deserializer,
|
|
||||||
call.receive()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
|
|
||||||
field: String
|
|
||||||
) = call.parameters[field].also {
|
|
||||||
if (it == null) {
|
|
||||||
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
|
|
||||||
field: String
|
|
||||||
) = call.request.queryParameters[field]
|
|
||||||
|
|
||||||
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
|
|
||||||
field: String
|
|
||||||
) = getQueryParameter(field).also {
|
|
||||||
if (it == null) {
|
|
||||||
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
|
|
||||||
field: String,
|
|
||||||
deserializer: DeserializationStrategy<T>
|
|
||||||
) = getQueryParameter(field) ?.let {
|
|
||||||
serialFormat.decodeHex(
|
|
||||||
deserializer,
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
|
|
||||||
field: String,
|
|
||||||
deserializer: DeserializationStrategy<T>
|
|
||||||
) = decodeUrlQueryValue(field, deserializer).also {
|
|
||||||
if (it == null) {
|
|
||||||
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val default
|
|
||||||
get() = defaultUnifiedRouter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val defaultUnifiedRouter = UnifiedRouter()
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <T> ApplicationCall.unianswer(
|
|
||||||
answerSerializer: SerializationStrategy<T>,
|
|
||||||
answer: T
|
|
||||||
) {
|
|
||||||
respondBytes (
|
|
||||||
standardKtorSerialFormat.encodeDefault(answerSerializer, answer),
|
|
||||||
standardKtorSerialFormatContentType
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <T> ApplicationCall.uniload(
|
|
||||||
deserializer: DeserializationStrategy<T>
|
|
||||||
) = safely {
|
|
||||||
standardKtorSerialFormat.decodeDefault(
|
|
||||||
deserializer,
|
|
||||||
receive()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun ApplicationCall.uniloadMultipart(
|
suspend fun ApplicationCall.uniloadMultipart(
|
||||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
@ -146,82 +46,6 @@ suspend fun ApplicationCall.uniloadMultipart(
|
|||||||
resultInput ?: error("Bytes has not been received")
|
resultInput ?: error("Bytes has not been received")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <T> ApplicationCall.uniloadMultipart(
|
|
||||||
deserializer: DeserializationStrategy<T>,
|
|
||||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
|
||||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
|
||||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
|
||||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
|
||||||
): Pair<Input, T> {
|
|
||||||
var data: Optional<T>? = null
|
|
||||||
val resultInput = uniloadMultipart(
|
|
||||||
onFormItem,
|
|
||||||
{
|
|
||||||
if (it.name == "data") {
|
|
||||||
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
|
||||||
} else {
|
|
||||||
onCustomFileItem(it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onBinaryChannelItem,
|
|
||||||
onBinaryContent
|
|
||||||
)
|
|
||||||
|
|
||||||
val completeData = data ?: error("Data has not been received")
|
|
||||||
return resultInput to (completeData.dataOrNull().let { it as T })
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <T> ApplicationCall.uniloadMultipartFile(
|
|
||||||
deserializer: DeserializationStrategy<T>,
|
|
||||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
|
||||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
|
||||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
|
||||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
|
||||||
) = safely {
|
|
||||||
val multipartData = receiveMultipart()
|
|
||||||
|
|
||||||
var resultInput: MPPFile? = null
|
|
||||||
var data: Optional<T>? = null
|
|
||||||
|
|
||||||
multipartData.forEachPart {
|
|
||||||
when (it) {
|
|
||||||
is PartData.FormItem -> onFormItem(it)
|
|
||||||
is PartData.FileItem -> {
|
|
||||||
when (it.name) {
|
|
||||||
"bytes" -> {
|
|
||||||
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
|
|
||||||
resultInput = MPPFile.createTempFile(
|
|
||||||
name.nameWithoutExtension.let {
|
|
||||||
var resultName = it
|
|
||||||
while (resultName.length < 3) {
|
|
||||||
resultName += "_"
|
|
||||||
}
|
|
||||||
resultName
|
|
||||||
},
|
|
||||||
".${name.extension}"
|
|
||||||
).apply {
|
|
||||||
outputStream().use { fileStream ->
|
|
||||||
it.streamProvider().use {
|
|
||||||
it.copyTo(fileStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
|
||||||
else -> onCustomFileItem(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is PartData.BinaryItem -> onBinaryContent(it)
|
|
||||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val completeData = data ?: error("Data has not been received")
|
|
||||||
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun ApplicationCall.uniloadMultipartFile(
|
suspend fun ApplicationCall.uniloadMultipartFile(
|
||||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
@ -285,24 +109,3 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
|
|||||||
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
fun <T> ApplicationCall.decodeUrlQueryValue(
|
|
||||||
field: String,
|
|
||||||
deserializer: DeserializationStrategy<T>
|
|
||||||
) = getQueryParameter(field) ?.let {
|
|
||||||
standardKtorSerialFormat.decodeHex(
|
|
||||||
deserializer,
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
|
||||||
suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError(
|
|
||||||
field: String,
|
|
||||||
deserializer: DeserializationStrategy<T>
|
|
||||||
) = decodeUrlQueryValue(field, deserializer).also {
|
|
||||||
if (it == null) {
|
|
||||||
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -26,7 +26,6 @@ import java.nio.file.attribute.FileTime
|
|||||||
|
|
||||||
class TemporalFilesRoutingConfigurator(
|
class TemporalFilesRoutingConfigurator(
|
||||||
private val subpath: String = DefaultTemporalFilesSubPath,
|
private val subpath: String = DefaultTemporalFilesSubPath,
|
||||||
private val unifiedRouter: UnifiedRouter = UnifiedRouter.default,
|
|
||||||
private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer
|
private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer
|
||||||
) : ApplicationRoutingConfigurator.Element {
|
) : ApplicationRoutingConfigurator.Element {
|
||||||
interface TemporalFilesUtilizer {
|
interface TemporalFilesUtilizer {
|
||||||
@ -80,7 +79,6 @@ class TemporalFilesRoutingConfigurator(
|
|||||||
|
|
||||||
override fun Route.invoke() {
|
override fun Route.invoke() {
|
||||||
post(subpath) {
|
post(subpath) {
|
||||||
unifiedRouter.apply {
|
|
||||||
val multipart = call.receiveMultipart()
|
val multipart = call.receiveMultipart()
|
||||||
|
|
||||||
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
|
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
|
||||||
@ -117,7 +115,6 @@ class TemporalFilesRoutingConfigurator(
|
|||||||
} ?: call.respond(HttpStatusCode.BadRequest)
|
} ?: call.respond(HttpStatusCode.BadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun removeTemporalFile(temporalFileId: TemporalFileId) {
|
suspend fun removeTemporalFile(temporalFileId: TemporalFileId) {
|
||||||
temporalFilesMutex.withLock {
|
temporalFilesMutex.withLock {
|
||||||
|
@ -33,18 +33,6 @@ data class PaginationResult<T>(
|
|||||||
results,
|
results,
|
||||||
(pagesNumber * size).toLong()
|
(pagesNumber * size).toLong()
|
||||||
)
|
)
|
||||||
@Deprecated("Replace with The other order of incoming parameters or objectsCount parameter")
|
|
||||||
constructor(
|
|
||||||
page: Int,
|
|
||||||
pagesNumber: Int,
|
|
||||||
results: List<T>,
|
|
||||||
size: Int
|
|
||||||
) : this(
|
|
||||||
page,
|
|
||||||
results,
|
|
||||||
pagesNumber,
|
|
||||||
size
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L)
|
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L)
|
||||||
|
@ -4,9 +4,9 @@ import dev.inmo.micro_utils.repos.UpdatedValuePair
|
|||||||
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.statements.*
|
||||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import java.util.Objects
|
||||||
|
|
||||||
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
flowsChannelsSize: Int = 0,
|
flowsChannelsSize: Int = 0,
|
||||||
@ -27,10 +27,31 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
|
|
||||||
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
|
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
|
||||||
|
|
||||||
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
|
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateBuilder<Int>)
|
||||||
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement)
|
protected abstract fun createAndInsertId(value: InputValueType, it: InsertStatement<Number>): IdType
|
||||||
|
|
||||||
|
protected open fun insert(value: InputValueType, it: InsertStatement<Number>) {
|
||||||
|
val id = createAndInsertId(value, it)
|
||||||
|
update(id, value, it as UpdateBuilder<Int>)
|
||||||
|
}
|
||||||
|
@Deprecated(
|
||||||
|
"Replace its \"it\" parameter type with \"UpdateBuilder<Int>\" to actualize method signature. Method with current signature will be removed soon and do not recommended to override anymore"
|
||||||
|
)
|
||||||
|
protected open fun update(id: IdType, value: InputValueType, it: UpdateStatement) = update(
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
it as UpdateBuilder<Int>
|
||||||
|
)
|
||||||
|
|
||||||
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
|
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this method to do the something with [values]. You may change and output values in that list and return
|
||||||
|
* changed list of pairs
|
||||||
|
*/
|
||||||
|
protected open suspend fun onAfterCreate(
|
||||||
|
values: List<Pair<InputValueType, ObjectType>>
|
||||||
|
): List<ObjectType> = values.map { it.second }
|
||||||
private fun createWithoutNotification(value: InputValueType): ObjectType {
|
private fun createWithoutNotification(value: InputValueType): ObjectType {
|
||||||
return transaction(database) {
|
return transaction(database) {
|
||||||
insert { insert(value, it) }.asObject(value)
|
insert { insert(value, it) }.asObject(value)
|
||||||
@ -40,13 +61,18 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||||
onBeforeCreate(values)
|
onBeforeCreate(values)
|
||||||
return transaction(db = database) {
|
return transaction(db = database) {
|
||||||
values.map { value -> createWithoutNotification(value) }
|
values.map { value -> value to createWithoutNotification(value) }
|
||||||
|
}.let {
|
||||||
|
onAfterCreate(it)
|
||||||
}.onEach {
|
}.onEach {
|
||||||
_newObjectsFlow.emit(it)
|
_newObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open suspend fun onBeforeUpdate(value: List<UpdatedValuePair<IdType, InputValueType>>) {}
|
protected open suspend fun onBeforeUpdate(value: List<UpdatedValuePair<IdType, InputValueType>>) {}
|
||||||
|
protected open suspend fun onAfterUpdate(
|
||||||
|
value: List<UpdatedValuePair<InputValueType, ObjectType>>
|
||||||
|
): List<ObjectType> = value.map { it.second }
|
||||||
private fun updateWithoutNotification(id: IdType, value: InputValueType): ObjectType? {
|
private fun updateWithoutNotification(id: IdType, value: InputValueType): ObjectType? {
|
||||||
return transaction(db = database) {
|
return transaction(db = database) {
|
||||||
update(
|
update(
|
||||||
@ -54,7 +80,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
selectById(this, id)
|
selectById(this, id)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
update(id, value, it)
|
update(id, value, it as UpdateBuilder<Int>)
|
||||||
}
|
}
|
||||||
}.let {
|
}.let {
|
||||||
if (it > 0) {
|
if (it > 0) {
|
||||||
@ -71,7 +97,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
|
|
||||||
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
||||||
onBeforeUpdate(listOf(id to value))
|
onBeforeUpdate(listOf(id to value))
|
||||||
return updateWithoutNotification(id, value).also {
|
return updateWithoutNotification(id, value).let {
|
||||||
|
onAfterUpdate(listOf(value to (it ?: return@let emptyList())))
|
||||||
|
}.firstOrNull().also {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
_updatedObjectsFlow.emit(it)
|
_updatedObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
@ -81,9 +109,11 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
onBeforeUpdate(values)
|
onBeforeUpdate(values)
|
||||||
return (
|
return (
|
||||||
transaction(db = database) {
|
transaction(db = database) {
|
||||||
values.map { (id, value) -> updateWithoutNotification(id, value) }
|
values.mapNotNull { (id, value) -> value to (updateWithoutNotification(id, value) ?: return@mapNotNull null) }
|
||||||
}.filterNotNull()
|
}
|
||||||
).onEach {
|
).let {
|
||||||
|
onAfterUpdate(it)
|
||||||
|
}.onEach {
|
||||||
_updatedObjectsFlow.emit(it)
|
_updatedObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
|
|||||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.statements.*
|
||||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
abstract class AbstractExposedKeyValueRepo<Key, Value>(
|
abstract class AbstractExposedKeyValueRepo<Key, Value>(
|
||||||
@ -20,13 +19,27 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
|
|||||||
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||||
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
|
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
|
||||||
|
|
||||||
protected abstract fun update(k: Key, v: Value, it: UpdateStatement)
|
protected abstract fun update(k: Key, v: Value, it: UpdateBuilder<Int>)
|
||||||
protected abstract fun insert(k: Key, v: Value, it: InsertStatement<Number>)
|
protected abstract fun insertKey(k: Key, v: Value, it: InsertStatement<Number>)
|
||||||
|
|
||||||
|
protected open fun insert(k: Key, v: Value, it: InsertStatement<Number>) {
|
||||||
|
insertKey(k, v, it)
|
||||||
|
update(k, v, it as UpdateBuilder<Int>)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"Replace its \"it\" parameter type with \"UpdateBuilder<Int>\" to actualize method signature. Method with current signature will be removed soon and do not recommended to override anymore"
|
||||||
|
)
|
||||||
|
protected open fun update(k: Key, v: Value, it: UpdateStatement) = update(
|
||||||
|
k,
|
||||||
|
v,
|
||||||
|
it as UpdateBuilder<Int>
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun set(toSet: Map<Key, Value>) {
|
override suspend fun set(toSet: Map<Key, Value>) {
|
||||||
transaction(database) {
|
transaction(database) {
|
||||||
toSet.mapNotNull { (k, v) ->
|
toSet.mapNotNull { (k, v) ->
|
||||||
if (update({ selectById(k) }) { update(k, v, it) } > 0) {
|
if (update({ selectById(k) }) { update(k, v, it as UpdateBuilder<Int>) } > 0) {
|
||||||
k to v
|
k to v
|
||||||
} else {
|
} else {
|
||||||
val inserted = insert {
|
val inserted = insert {
|
||||||
|
@ -5,57 +5,27 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
|||||||
import dev.inmo.micro_utils.repos.exposed.*
|
import dev.inmo.micro_utils.repos.exposed.*
|
||||||
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
|
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||||
|
import org.jetbrains.exposed.sql.statements.UpdateBuilder
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
open class ExposedReadKeyValueRepo<Key, Value>(
|
open class ExposedReadKeyValueRepo<Key, Value>(
|
||||||
override val database: Database,
|
database: Database,
|
||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
tableName: String? = null
|
tableName: String? = null
|
||||||
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValueRepo<Key, Value>(database, tableName) {
|
||||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
|
||||||
|
override val keyColumn: Column<Key> = keyColumnAllocator()
|
||||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
override val ResultRow.asKey: Key
|
||||||
|
get() = get(keyColumn)
|
||||||
|
override val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
|
||||||
|
override val ResultRow.asObject: Value
|
||||||
|
get() = get(valueColumn)
|
||||||
|
override val selectById: SqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
|
||||||
|
override val primaryKey: Table.PrimaryKey
|
||||||
|
get() = PrimaryKey(keyColumn, valueColumn)
|
||||||
|
|
||||||
init { initTable() }
|
init { initTable() }
|
||||||
|
|
||||||
override suspend fun get(k: Key): Value? = transaction(database) {
|
|
||||||
select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun contains(key: Key): Boolean = transaction(database) {
|
|
||||||
select { keyColumn.eq(key) }.limit(1).any()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun count(): Long = transaction(database) { selectAll().count() }
|
|
||||||
|
|
||||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
|
||||||
selectAll().selectPaginated(
|
|
||||||
pagination,
|
|
||||||
keyColumn,
|
|
||||||
reversed
|
|
||||||
) {
|
|
||||||
it[keyColumn]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
|
||||||
select { valueColumn.eq(v) }.selectPaginated(
|
|
||||||
pagination,
|
|
||||||
keyColumn,
|
|
||||||
reversed
|
|
||||||
) {
|
|
||||||
it[keyColumn]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
|
|
||||||
selectAll().selectPaginated(
|
|
||||||
pagination,
|
|
||||||
keyColumn,
|
|
||||||
reversed
|
|
||||||
) {
|
|
||||||
it[valueColumn]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.exposed.*
|
import dev.inmo.micro_utils.repos.exposed.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
|
|
||||||
typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value>
|
typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value>
|
||||||
|
|
||||||
@ -13,54 +11,15 @@ open class ExposedReadKeyValuesRepo<Key, Value>(
|
|||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
tableName: String? = null
|
tableName: String? = null
|
||||||
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValuesRepo<Key, Value>(database, tableName) {
|
||||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
override val keyColumn: Column<Key> = keyColumnAllocator()
|
||||||
|
override val ResultRow.asKey: Key
|
||||||
|
get() = get(keyColumn)
|
||||||
|
override val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
|
||||||
|
override val ResultRow.asObject: Value
|
||||||
|
get() = get(valueColumn)
|
||||||
|
override val selectById: SqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
|
||||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||||
|
|
||||||
init { initTable() }
|
init { initTable() }
|
||||||
|
|
||||||
override suspend fun count(k: Key): Long = transaction(database) { select { keyColumn.eq(k) }.count() }
|
|
||||||
|
|
||||||
override suspend fun count(): Long = transaction(database) { selectAll().count() }
|
|
||||||
|
|
||||||
override suspend fun get(
|
|
||||||
k: Key,
|
|
||||||
pagination: Pagination,
|
|
||||||
reversed: Boolean
|
|
||||||
): PaginationResult<Value> = transaction(database) {
|
|
||||||
select { keyColumn.eq(k) }.paginate(pagination, keyColumn, reversed).map { it[valueColumn] }
|
|
||||||
}.createPaginationResult(
|
|
||||||
pagination,
|
|
||||||
count(k)
|
|
||||||
)
|
|
||||||
|
|
||||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
|
||||||
selectAll().paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
|
|
||||||
}.createPaginationResult(
|
|
||||||
pagination,
|
|
||||||
count()
|
|
||||||
)
|
|
||||||
|
|
||||||
override suspend fun keys(
|
|
||||||
v: Value,
|
|
||||||
pagination: Pagination,
|
|
||||||
reversed: Boolean
|
|
||||||
): PaginationResult<Key> = transaction(database) {
|
|
||||||
select { valueColumn.eq(v) }.let {
|
|
||||||
it.count() to it.paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
|
|
||||||
}
|
|
||||||
}.let { (count, list) ->
|
|
||||||
list.createPaginationResult(
|
|
||||||
pagination,
|
|
||||||
count
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun contains(k: Key): Boolean = transaction(database) {
|
|
||||||
select { keyColumn.eq(k) }.limit(1).any()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
|
|
||||||
select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).any()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
|
||||||
import dev.inmo.micro_utils.pagination.extractPagination
|
|
||||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.countRouting
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.server.application.call
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.get
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
|
|
||||||
fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes(
|
|
||||||
originalRepo: ReadCRUDRepo<ObjectType, IdType>,
|
|
||||||
objectsSerializer: KSerializer<ObjectType>,
|
|
||||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
|
||||||
idsSerializer: KSerializer<IdType>,
|
|
||||||
unifiedRouter: UnifiedRouter
|
|
||||||
) {
|
|
||||||
val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
|
|
||||||
|
|
||||||
get(getByPaginationRouting) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val pagination = call.request.queryParameters.extractPagination
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
paginationResultSerializer,
|
|
||||||
originalRepo.getByPagination(pagination)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(getByIdRouting) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val id = decodeUrlQueryValueOrSendError(
|
|
||||||
"id",
|
|
||||||
idsSerializer
|
|
||||||
) ?: return@get
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
objectsNullableSerializer,
|
|
||||||
originalRepo.getById(id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(containsRouting) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val id = decodeUrlQueryValueOrSendError(
|
|
||||||
"id",
|
|
||||||
idsSerializer
|
|
||||||
) ?: return@get
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Boolean.serializer(),
|
|
||||||
originalRepo.contains(id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(countRouting) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
unianswer(
|
|
||||||
Long.serializer(),
|
|
||||||
originalRepo.count()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes(
|
|
||||||
originalRepo: ReadCRUDRepo<ObjectType, IdType>,
|
|
||||||
objectsSerializer: KSerializer<ObjectType>,
|
|
||||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
|
||||||
idsSerializer: KSerializer<IdType>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) = configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
|
@ -1,39 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
|
||||||
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
|
|
||||||
import dev.inmo.micro_utils.repos.CRUDRepo
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.route
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes(
|
|
||||||
baseSubpart: String,
|
|
||||||
originalRepo: CRUDRepo<ObjectType, IdType, InputValue>,
|
|
||||||
objectsSerializer: KSerializer<ObjectType>,
|
|
||||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
|
||||||
inputsSerializer: KSerializer<InputValue>,
|
|
||||||
idsSerializer: KSerializer<IdType>,
|
|
||||||
unifiedRouter: UnifiedRouter
|
|
||||||
) {
|
|
||||||
route(baseSubpart) {
|
|
||||||
configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, unifiedRouter)
|
|
||||||
configureWriteCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, unifiedRouter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes(
|
|
||||||
baseSubpart: String,
|
|
||||||
originalRepo: CRUDRepo<ObjectType, IdType, InputValue>,
|
|
||||||
objectsSerializer: KSerializer<ObjectType>,
|
|
||||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
|
||||||
inputsSerializer: KSerializer<InputValue>,
|
|
||||||
idsSerializer: KSerializer<IdType>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) = configureCRUDRepoRoutes(
|
|
||||||
baseSubpart, originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
|
||||||
)
|
|
@ -1,107 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
|
||||||
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.post
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.builtins.*
|
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputValue> Route.configureWriteCRUDRepoRoutes(
|
|
||||||
originalRepo: WriteCRUDRepo<ObjectType, IdType, InputValue>,
|
|
||||||
objectsSerializer: KSerializer<ObjectType>,
|
|
||||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
|
||||||
inputsSerializer: KSerializer<InputValue>,
|
|
||||||
idsSerializer: KSerializer<IdType>,
|
|
||||||
unifiedRouter: UnifiedRouter
|
|
||||||
) {
|
|
||||||
val listObjectsSerializer = ListSerializer(objectsSerializer)
|
|
||||||
val listInputSerializer = ListSerializer(inputsSerializer)
|
|
||||||
val listIdsSerializer = ListSerializer(idsSerializer)
|
|
||||||
val inputUpdateSerializer = PairSerializer(
|
|
||||||
idsSerializer,
|
|
||||||
inputsSerializer
|
|
||||||
)
|
|
||||||
val listInputUpdateSerializer = ListSerializer(inputUpdateSerializer)
|
|
||||||
|
|
||||||
unifiedRouter.apply {
|
|
||||||
includeWebsocketHandling(
|
|
||||||
newObjectsFlowRouting,
|
|
||||||
originalRepo.newObjectsFlow,
|
|
||||||
objectsSerializer
|
|
||||||
)
|
|
||||||
includeWebsocketHandling(
|
|
||||||
updatedObjectsFlowRouting,
|
|
||||||
originalRepo.updatedObjectsFlow,
|
|
||||||
objectsSerializer
|
|
||||||
)
|
|
||||||
includeWebsocketHandling(
|
|
||||||
deletedObjectsIdsFlowRouting,
|
|
||||||
originalRepo.deletedObjectsIdsFlow,
|
|
||||||
idsSerializer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
post(createRouting) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
unianswer(
|
|
||||||
listObjectsSerializer,
|
|
||||||
originalRepo.create(
|
|
||||||
uniload(listInputSerializer)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post(updateRouting) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val (id, input) = uniload(inputUpdateSerializer)
|
|
||||||
unianswer(
|
|
||||||
objectsNullableSerializer,
|
|
||||||
originalRepo.update(
|
|
||||||
id, input
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post(updateManyRouting) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val updates = uniload(listInputUpdateSerializer)
|
|
||||||
unianswer(
|
|
||||||
listObjectsSerializer,
|
|
||||||
originalRepo.update(
|
|
||||||
updates
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post(deleteByIdRouting) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val ids = uniload(listIdsSerializer)
|
|
||||||
unianswer(
|
|
||||||
Unit.serializer(),
|
|
||||||
originalRepo.deleteById(
|
|
||||||
ids
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputValue> Route.configureWriteCRUDRepoRoutes(
|
|
||||||
originalRepo: WriteCRUDRepo<ObjectType, IdType, InputValue>,
|
|
||||||
objectsSerializer: KSerializer<ObjectType>,
|
|
||||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
|
||||||
inputsSerializer: KSerializer<InputValue>,
|
|
||||||
idsSerializer: KSerializer<IdType>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) = configureWriteCRUDRepoRoutes(
|
|
||||||
originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
|
||||||
)
|
|
@ -1,46 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
|
||||||
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
|
|
||||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.route
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
|
|
||||||
fun <K, V> Route.configureKeyValueRepoRoutes(
|
|
||||||
baseSubpart: String,
|
|
||||||
originalRepo: KeyValueRepo<K, V>,
|
|
||||||
keySerializer: KSerializer<K>,
|
|
||||||
valueSerializer: KSerializer<V>,
|
|
||||||
valueNullableSerializer: KSerializer<V?>,
|
|
||||||
unifiedRouter: UnifiedRouter
|
|
||||||
) {
|
|
||||||
route(baseSubpart) {
|
|
||||||
configureReadStandartKeyValueRepoRoutes(
|
|
||||||
originalRepo,
|
|
||||||
keySerializer,
|
|
||||||
valueSerializer,
|
|
||||||
valueNullableSerializer,
|
|
||||||
unifiedRouter
|
|
||||||
)
|
|
||||||
configureWriteKeyValueRepoRoutes(
|
|
||||||
originalRepo,
|
|
||||||
keySerializer,
|
|
||||||
valueSerializer,
|
|
||||||
unifiedRouter
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <K, V> Route.configureStandartKeyValueRepoRoutes(
|
|
||||||
baseSubpart: String,
|
|
||||||
originalRepo: KeyValueRepo<K, V>,
|
|
||||||
keySerializer: KSerializer<K>,
|
|
||||||
valueSerializer: KSerializer<V>,
|
|
||||||
valueNullableSerializer: KSerializer<V?>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) = configureKeyValueRepoRoutes(baseSubpart, originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
|
@ -1,107 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
|
||||||
import dev.inmo.micro_utils.pagination.extractPagination
|
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.*
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.containsRoute
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.countRoute
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.keyParameterName
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.reversedParameterName
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.server.application.call
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.get
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
|
|
||||||
fun <K, V> Route.configureReadStandartKeyValueRepoRoutes (
|
|
||||||
originalRepo: ReadKeyValueRepo<K, V>,
|
|
||||||
keySerializer: KSerializer<K>,
|
|
||||||
valueSerializer: KSerializer<V>,
|
|
||||||
valueNullableSerializer: KSerializer<V?>,
|
|
||||||
unifiedRouter: UnifiedRouter
|
|
||||||
) {
|
|
||||||
get(getRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val key = decodeUrlQueryValueOrSendError(
|
|
||||||
keyParameterName,
|
|
||||||
keySerializer
|
|
||||||
) ?: return@get
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
valueNullableSerializer,
|
|
||||||
originalRepo.get(key)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(valuesRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val parination = call.request.queryParameters.extractPagination;
|
|
||||||
val reversed = decodeUrlQueryValueOrSendError(
|
|
||||||
reversedParameterName,
|
|
||||||
Boolean.serializer()
|
|
||||||
) ?: return@get
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
PaginationResult.serializer(valueSerializer),
|
|
||||||
originalRepo.values(parination, reversed)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(keysRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val parination = call.request.queryParameters.extractPagination;
|
|
||||||
val reversed = decodeUrlQueryValueOrSendError(
|
|
||||||
reversedParameterName,
|
|
||||||
Boolean.serializer()
|
|
||||||
) ?: return@get
|
|
||||||
val value = decodeUrlQueryValue(valueParameterName, valueSerializer)
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
PaginationResult.serializer(keySerializer),
|
|
||||||
value?.let { originalRepo.keys(value, parination, reversed) } ?: originalRepo.keys(parination, reversed)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(containsRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val key = decodeUrlQueryValueOrSendError(
|
|
||||||
keyParameterName,
|
|
||||||
keySerializer
|
|
||||||
) ?: return@get
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Boolean.serializer(),
|
|
||||||
originalRepo.contains(key)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(countRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
unianswer(
|
|
||||||
Long.serializer(),
|
|
||||||
originalRepo.count()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <K, V> Route.configureReadStandartKeyValueRepoRoutes (
|
|
||||||
originalRepo: ReadKeyValueRepo<K, V>,
|
|
||||||
keySerializer: KSerializer<K>,
|
|
||||||
valueSerializer: KSerializer<V>,
|
|
||||||
valueNullableSerializer: KSerializer<V?>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) = configureReadStandartKeyValueRepoRoutes(
|
|
||||||
originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
|
||||||
)
|
|
@ -1,70 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
|
||||||
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.post
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.builtins.*
|
|
||||||
|
|
||||||
fun <K, V> Route.configureWriteKeyValueRepoRoutes (
|
|
||||||
originalRepo: WriteKeyValueRepo<K, V>,
|
|
||||||
keySerializer: KSerializer<K>,
|
|
||||||
valueSerializer: KSerializer<V>,
|
|
||||||
unifiedRouter: UnifiedRouter
|
|
||||||
) {
|
|
||||||
val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
|
||||||
val keysListSerializer = ListSerializer(keySerializer)
|
|
||||||
val valuesListSerializer = ListSerializer(valueSerializer)
|
|
||||||
unifiedRouter.apply {
|
|
||||||
includeWebsocketHandling(
|
|
||||||
onNewValueRoute,
|
|
||||||
originalRepo.onNewValue,
|
|
||||||
PairSerializer(keySerializer, valueSerializer)
|
|
||||||
)
|
|
||||||
|
|
||||||
includeWebsocketHandling(
|
|
||||||
onValueRemovedRoute,
|
|
||||||
originalRepo.onValueRemoved,
|
|
||||||
keySerializer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
post(setRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val toSet = uniload(
|
|
||||||
keyValueMapSerializer
|
|
||||||
)
|
|
||||||
|
|
||||||
unianswer(Unit.serializer(), originalRepo.set(toSet))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post(unsetRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val toUnset = uniload(keysListSerializer)
|
|
||||||
|
|
||||||
unianswer(Unit.serializer(), originalRepo.unset(toUnset))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post(unsetWithValuesRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val toUnset = uniload(valuesListSerializer)
|
|
||||||
|
|
||||||
unianswer(Unit.serializer(), originalRepo.unsetWithValues(toUnset))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <K, V> Route.configureWriteStandartKeyValueRepoRoutes (
|
|
||||||
originalRepo: WriteKeyValueRepo<K, V>,
|
|
||||||
keySerializer: KSerializer<K>,
|
|
||||||
valueSerializer: KSerializer<V>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) = configureWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
|
@ -1,35 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
|
||||||
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
|
|
||||||
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.route
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
|
|
||||||
fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes(
|
|
||||||
baseSubpart: String,
|
|
||||||
originalRepo: KeyValuesRepo<Key, Value>,
|
|
||||||
keySerializer: KSerializer<Key>,
|
|
||||||
valueSerializer: KSerializer<Value>,
|
|
||||||
unifiedRouter: UnifiedRouter
|
|
||||||
) {
|
|
||||||
route(baseSubpart) {
|
|
||||||
configureOneToManyReadKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, unifiedRouter)
|
|
||||||
configureOneToManyWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, unifiedRouter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes(
|
|
||||||
baseSubpart: String,
|
|
||||||
originalRepo: KeyValuesRepo<Key, Value>,
|
|
||||||
keySerializer: KSerializer<Key>,
|
|
||||||
valueSerializer: KSerializer<Value>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) = configureOneToManyKeyValueRepoRoutes(
|
|
||||||
baseSubpart, originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
|
||||||
)
|
|
@ -1,129 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
|
||||||
import dev.inmo.micro_utils.pagination.extractPagination
|
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.server.application.call
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.get
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
|
|
||||||
fun <Key, Value> Route.configureOneToManyReadKeyValueRepoRoutes(
|
|
||||||
originalRepo: ReadKeyValuesRepo<Key, Value>,
|
|
||||||
keySerializer: KSerializer<Key>,
|
|
||||||
valueSerializer: KSerializer<Value>,
|
|
||||||
unifiedRouter: UnifiedRouter
|
|
||||||
) {
|
|
||||||
val paginationKeyResult = PaginationResult.serializer(keySerializer)
|
|
||||||
val paginationValueResult = PaginationResult.serializer(valueSerializer)
|
|
||||||
|
|
||||||
get(getRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val pagination = call.request.queryParameters.extractPagination
|
|
||||||
val key = decodeUrlQueryValueOrSendError(
|
|
||||||
keyParameterName,
|
|
||||||
keySerializer
|
|
||||||
) ?: return@get
|
|
||||||
val reversed = decodeUrlQueryValue(
|
|
||||||
reversedParameterName,
|
|
||||||
Boolean.serializer()
|
|
||||||
) ?: false
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
paginationValueResult,
|
|
||||||
originalRepo.get(key, pagination, reversed)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(keysRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val pagination = call.request.queryParameters.extractPagination
|
|
||||||
val reversed = decodeUrlQueryValue(
|
|
||||||
reversedParameterName,
|
|
||||||
Boolean.serializer()
|
|
||||||
) ?: false
|
|
||||||
val value: Value? = decodeUrlQueryValue(
|
|
||||||
valueParameterName,
|
|
||||||
valueSerializer
|
|
||||||
)
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
paginationKeyResult,
|
|
||||||
value?.let { originalRepo.keys(value, pagination, reversed) } ?: originalRepo.keys(pagination, reversed)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(containsByKeyRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val key = decodeUrlQueryValueOrSendError(
|
|
||||||
keyParameterName,
|
|
||||||
keySerializer
|
|
||||||
) ?: return@get
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Boolean.serializer(),
|
|
||||||
originalRepo.contains(key)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(containsByKeyValueRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val key = decodeUrlQueryValueOrSendError(
|
|
||||||
keyParameterName,
|
|
||||||
keySerializer
|
|
||||||
) ?: return@get
|
|
||||||
val value = decodeUrlQueryValueOrSendError(
|
|
||||||
valueParameterName,
|
|
||||||
valueSerializer
|
|
||||||
) ?: return@get
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Boolean.serializer(),
|
|
||||||
originalRepo.contains(key, value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(countByKeyRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val key = decodeUrlQueryValueOrSendError(
|
|
||||||
keyParameterName,
|
|
||||||
keySerializer
|
|
||||||
) ?: return@get
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Long.serializer(),
|
|
||||||
originalRepo.count(key)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(countRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
unianswer(
|
|
||||||
Long.serializer(),
|
|
||||||
originalRepo.count()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <Key, Value> Route.configureOneToManyReadKeyValueRepoRoutes(
|
|
||||||
originalRepo: ReadKeyValuesRepo<Key, Value>,
|
|
||||||
keySerializer: KSerializer<Key>,
|
|
||||||
valueSerializer: KSerializer<Value>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) = configureOneToManyReadKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
|
@ -1,103 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
|
||||||
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
|
||||||
import io.ktor.http.ContentType
|
|
||||||
import io.ktor.server.routing.Route
|
|
||||||
import io.ktor.server.routing.post
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.builtins.*
|
|
||||||
|
|
||||||
fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
|
|
||||||
originalRepo: WriteKeyValuesRepo<Key, Value>,
|
|
||||||
keySerializer: KSerializer<Key>,
|
|
||||||
valueSerializer: KSerializer<Value>,
|
|
||||||
unifiedRouter: UnifiedRouter
|
|
||||||
) {
|
|
||||||
val keyValueSerializer = PairSerializer(keySerializer, valueSerializer)
|
|
||||||
val keyValueMapSerializer = MapSerializer(keySerializer, ListSerializer(valueSerializer))
|
|
||||||
|
|
||||||
unifiedRouter.apply {
|
|
||||||
includeWebsocketHandling(
|
|
||||||
onNewValueRoute,
|
|
||||||
originalRepo.onNewValue,
|
|
||||||
keyValueSerializer
|
|
||||||
)
|
|
||||||
includeWebsocketHandling(
|
|
||||||
onValueRemovedRoute,
|
|
||||||
originalRepo.onValueRemoved,
|
|
||||||
keyValueSerializer
|
|
||||||
)
|
|
||||||
includeWebsocketHandling(
|
|
||||||
onDataClearedRoute,
|
|
||||||
originalRepo.onDataCleared,
|
|
||||||
keySerializer
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
post(addRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val obj = uniload(keyValueMapSerializer)
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Unit.serializer(),
|
|
||||||
originalRepo.add(obj)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post(removeRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val obj = uniload(keyValueMapSerializer)
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Unit.serializer(),
|
|
||||||
originalRepo.remove(obj),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post(clearRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val key = uniload(keySerializer)
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Unit.serializer(),
|
|
||||||
originalRepo.clear(key),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post(clearWithValueRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val v = uniload(valueSerializer)
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Unit.serializer(),
|
|
||||||
originalRepo.clearWithValue(v),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post(setRoute) {
|
|
||||||
unifiedRouter.apply {
|
|
||||||
val obj = uniload(keyValueMapSerializer)
|
|
||||||
|
|
||||||
unianswer(
|
|
||||||
Unit.serializer(),
|
|
||||||
originalRepo.set(obj)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
|
|
||||||
originalRepo: WriteKeyValuesRepo<Key, Value>,
|
|
||||||
keySerializer: KSerializer<Key>,
|
|
||||||
valueSerializer: KSerializer<Value>,
|
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
||||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
|
||||||
) = configureOneToManyWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
|
Loading…
Reference in New Issue
Block a user