Skip to content

Commit

Permalink
feat(#43): rsocket transport
Browse files Browse the repository at this point in the history
  • Loading branch information
y9vad9 committed Sep 27, 2023
1 parent ede0c53 commit 4d12f6c
Show file tree
Hide file tree
Showing 138 changed files with 2,198 additions and 108 deletions.
6 changes: 4 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[versions]
kotlin = "1.8.21"
kotlin = "1.9.20-Beta"
kotlinx-coroutines = "1.6.4"
kotlinx-serialization = "1.4.1"
ktor = "2.0.1"
kafka = "3.3.1"
jupiter = "5.4.0"
exposed = "0.41.1"
kmongo = "4.8.0"
androidGradlePlugin = "8.2.0-alpha13"
androidGradlePlugin = "8.3.0-alpha05"
androidComposeVersion = "1.4.0-alpha02"
grpc = "1.3.0"
protobuf = "3.22.0"
Expand Down Expand Up @@ -66,6 +66,8 @@ grpc-android = { module = "io.grpc:grpc-android", version.require = "1.56.1" }
protobuf-kotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf" }
protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }

rsocket-client = { module = "io.rsocket.kotlin:rsocket-ktor-client", version.require = "0.15.4" }

[plugins]
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-rc-2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public class GrpcTimeMatesRequestsEngine(
.setVerificationHash(request.verificationHash.string)
.build()
).let { response ->
ConfirmAuthorizationRequest.Response(
ConfirmAuthorizationRequest.Result(
isNewAccount = response.isNewAccount,
authorization = response.authorization.takeIf { !response.isNewAccount }
?.let(authMapper::grpcAuthorizationToSdkAuthorization),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.timemates.api.grpc.mappers

import io.timemates.api.authorizations.types.AuthorizationOuterClass.Authorization
import io.timemates.sdk.authorization.sessions.types.value.ClientIpAddress
import io.timemates.sdk.authorization.sessions.types.value.ClientName
import io.timemates.sdk.authorization.sessions.types.value.ApplicationName
import io.timemates.sdk.authorization.sessions.types.value.ClientVersion
import io.timemates.sdk.authorization.types.value.HashValue
import io.timemates.sdk.common.constructor.createOrThrow
Expand Down Expand Up @@ -31,7 +31,7 @@ internal class AuthorizationsMapper {
metadata: Authorization.Metadata,
): SdkAuthorization.Metadata = with(metadata) {
return@with SdkAuthorization.Metadata(
clientName = ClientName.createOrThrow(clientName),
applicationName = ApplicationName.createOrThrow(clientName),
clientVersion = ClientVersion.createOrThrow(clientVersion),
clientIpAddress = ClientIpAddress.createOrThrow(clientIpAddress),
)
Expand Down
20 changes: 20 additions & 0 deletions rsocket-engine/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlinx.serialization)
alias(libs.plugins.library.publish)
}

kotlin {
jvm()
jvmToolchain(17)

explicitApi()
}

dependencies {
commonMainImplementation(projects.sdk)

commonMainImplementation(libs.kotlinx.serialization)
commonMainImplementation(libs.kotlinx.datetime)
commonMainImplementation(libs.rsocket.client)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.timemates.api.rsocket

import io.timemates.api.rsocket.authorizations.commands.authorizations
import io.timemates.api.rsocket.common.commands.rSocketCommands
import io.timemates.api.rsocket.files.commands.files
import io.timemates.api.rsocket.timers.commands.timers
import io.timemates.api.rsocket.users.commands.users

/**
* Registry for RSocket commands used in the TimeMates application.
*
* The [rSocketCommandsRegistry] is responsible for initializing and registering RSocket commands
* for specific TimeMates features or functionalities.
*/
internal val rSocketCommandsRegistry = rSocketCommands {
authorizations()
users()
files()
timers()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.timemates.api.rsocket

import io.ktor.client.HttpClient
import io.rsocket.kotlin.ExperimentalMetadataApi
import io.rsocket.kotlin.RSocket
import io.rsocket.kotlin.ktor.client.rSocket
import io.rsocket.kotlin.metadata.RoutingMetadata
import io.rsocket.kotlin.metadata.compositeMetadata
import io.rsocket.kotlin.payload.buildPayload
import io.timemates.api.rsocket.common.markers.RSocketRequest
import io.timemates.api.rsocket.common.metadata.AuthorizationMetadata
import io.timemates.api.rsocket.common.serialization.decodeFromJson
import io.timemates.api.rsocket.common.serialization.encodeToJson
import io.timemates.api.rsocket.timers.types.sdk
import io.timemates.sdk.common.engine.TimeMatesRequestsEngine
import io.timemates.sdk.common.exceptions.UnsupportedException
import io.timemates.sdk.common.types.Empty
import io.timemates.sdk.common.types.TimeMatesEntity
import io.timemates.sdk.common.types.TimeMatesRequest
import io.timemates.sdk.timers.requests.EditTimerRequest
import io.timemates.sdk.timers.requests.GetTimerRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.serializer
import io.timemates.api.rsocket.timers.requests.RSocketEditTimerRequest as RSocketEditTimerRequest
import io.timemates.api.rsocket.timers.requests.RSocketGetTimerRequest as RSocketGetTimerRequest

/**
* Represents an engine for making requests using RSocket.
*
* @property client An instance of [HttpClient] for making HTTP requests.
* @param endpoint The RSocket endpoint URL. Defaults to "wss://api.timemates.io/v0/rsocket".
* @param coroutineScope A [CoroutineScope] tied to the RSocket lifecycle.
*/
public class RSocketTimeMatesRequestsEngine private constructor(
private val client: HttpClient,
endpoint: String = "wss://api.timemates.io/v0/rsocket",
coroutineScope: CoroutineScope,
) : TimeMatesRequestsEngine {
public companion object {
public const val API_VERSION: Int = 1
}

private val rSocket = coroutineScope.async(start = CoroutineStart.LAZY) {
client.rSocket(endpoint)
}

override suspend fun <T : TimeMatesEntity> execute(
request: TimeMatesRequest<T>,
): Result<T> = runCatching {
val rSocket = rSocket.await()
return@runCatching rSocketCommandsRegistry.execute(rSocket, request)
?: throw UnsupportedException("This type of request is not supported in RSocket engine.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.timemates.api.rsocket.authorizations.commands

import io.timemates.api.rsocket.common.commands.RSocketCommandsBuilderScope
import io.timemates.sdk.authorization.email.requests.ConfigureNewAccountRequest
import io.timemates.sdk.authorization.email.requests.ConfirmAuthorizationRequest
import io.timemates.sdk.authorization.email.requests.StartAuthorizationRequest
import io.timemates.sdk.authorization.sessions.requests.GetAuthorizationSessionsRequest
import io.timemates.sdk.authorization.sessions.requests.RenewAuthorizationRequest
import io.timemates.sdk.authorization.sessions.requests.TerminateCurrentAuthorizationSessionRequest
import io.timemates.sdk.common.annotations.ExperimentalTimeMatesApi

/**
* The commands that is connected to the authorization feature.
*/
@OptIn(ExperimentalTimeMatesApi::class)
internal fun RSocketCommandsBuilderScope.authorizations() {
StartAuthorizationCommand associatedWith StartAuthorizationRequest
ConfirmAuthorizationCommand associatedWith ConfirmAuthorizationRequest
ConfigureNewAccountCommand associatedWith ConfigureNewAccountRequest
GetAuthorizationSessionsCommand associatedWith GetAuthorizationSessionsRequest
TerminateCurrentAuthorizationCommand associatedWith TerminateCurrentAuthorizationSessionRequest
RenewAuthorizationCommand associatedWith RenewAuthorizationRequest
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.timemates.api.rsocket.authorizations.commands

import io.rsocket.kotlin.RSocket
import io.timemates.api.rsocket.authorizations.types.sdk
import io.timemates.api.rsocket.common.commands.RSocketCommand
import io.timemates.api.rsocket.common.ext.requestResponse
import io.timemates.sdk.authorization.email.requests.ConfigureNewAccountRequest
import io.timemates.api.rsocket.authorizations.requests.ConfigureAccountRequest as RSocketConfigureAccountRequest

internal object ConfigureNewAccountCommand : RSocketCommand<ConfigureNewAccountRequest, ConfigureNewAccountRequest.Result> {
override suspend fun execute(rSocket: RSocket, input: ConfigureNewAccountRequest): ConfigureNewAccountRequest.Result {
return rSocket.requestResponse<RSocketConfigureAccountRequest, RSocketConfigureAccountRequest.Result>(
route = "authorizations.account.configure",
data = RSocketConfigureAccountRequest(
verificationHash = input.verificationHash.string,
name = input.name.string,
description = input.description?.string,
)
).let { result ->
ConfigureNewAccountRequest.Result(
authorization = result.authorization.sdk()
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.timemates.api.rsocket.authorizations.commands

import io.rsocket.kotlin.RSocket
import io.timemates.api.rsocket.authorizations.types.sdk
import io.timemates.api.rsocket.common.commands.RSocketCommand
import io.timemates.api.rsocket.common.ext.requestResponse
import io.timemates.sdk.authorization.email.requests.ConfirmAuthorizationRequest
import io.timemates.api.rsocket.authorizations.requests.ConfirmAuthorizationRequest as RSocketConfirmAuthorizationRequest

internal object ConfirmAuthorizationCommand : RSocketCommand<ConfirmAuthorizationRequest, ConfirmAuthorizationRequest.Result> {
override suspend fun execute(rSocket: RSocket, input: ConfirmAuthorizationRequest): ConfirmAuthorizationRequest.Result {
return rSocket.requestResponse(
route = "authorizations.email.confirm",
data = RSocketConfirmAuthorizationRequest(
input.verificationHash.string, input.confirmationCode.string
)
).let { result ->
ConfirmAuthorizationRequest.Result(
isNewAccount = result.isNewAccount,
authorization = result.authorization?.sdk(),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.timemates.api.rsocket.authorizations.commands

import io.rsocket.kotlin.RSocket
import io.timemates.api.rsocket.authorizations.requests.GetAuthorizationsRequest
import io.timemates.api.rsocket.authorizations.types.SerializableAuthorization
import io.timemates.api.rsocket.authorizations.types.sdk
import io.timemates.api.rsocket.common.commands.RSocketCommand
import io.timemates.api.rsocket.common.ext.requestResponse
import io.timemates.sdk.authorization.sessions.requests.GetAuthorizationSessionsRequest
import io.timemates.sdk.authorization.sessions.types.Authorization
import io.timemates.sdk.common.constructor.createOrThrow
import io.timemates.sdk.common.pagination.Page
import io.timemates.sdk.common.pagination.PageToken

internal object GetAuthorizationSessionsCommand : RSocketCommand<GetAuthorizationSessionsRequest, Page<Authorization>> {
override suspend fun execute(rSocket: RSocket, input: GetAuthorizationSessionsRequest): Page<Authorization> {
return rSocket.requestResponse(
route = "authorizations.list",
data = GetAuthorizationsRequest(input.nextPageToken?.string),
accessHash = input.accessHash.string,
).let { result ->
Page(
results = result.list.map(SerializableAuthorization::sdk),
nextPageToken = result.nextPageToken?.let { PageToken.createOrThrow(it) }
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.timemates.api.rsocket.authorizations.commands

import io.rsocket.kotlin.RSocket
import io.timemates.api.rsocket.common.commands.RSocketCommand
import io.timemates.api.rsocket.common.ext.requestResponse
import io.timemates.sdk.authorization.sessions.requests.RenewAuthorizationRequest
import io.timemates.sdk.authorization.types.value.AccessHash
import io.timemates.sdk.common.annotations.ExperimentalTimeMatesApi
import io.timemates.sdk.common.constructor.createOrThrow
import io.timemates.api.rsocket.authorizations.requests.RenewAuthorizationRequest as RSocketRenewAuthorizationRequest

@OptIn(ExperimentalTimeMatesApi::class)
internal object RenewAuthorizationCommand : RSocketCommand<RenewAuthorizationRequest, RenewAuthorizationRequest.Result> {
override suspend fun execute(rSocket: RSocket, input: RenewAuthorizationRequest): RenewAuthorizationRequest.Result {
return rSocket.requestResponse(
route = "authorizations.renew",
data = RSocketRenewAuthorizationRequest(input.refreshHash.string),
).let { result ->
RenewAuthorizationRequest.Result(
AccessHash.createOrThrow(result.accessHash)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.timemates.api.rsocket.authorizations.commands

import io.rsocket.kotlin.RSocket
import io.timemates.api.rsocket.authorizations.types.serializable
import io.timemates.api.rsocket.common.commands.RSocketCommand
import io.timemates.api.rsocket.common.ext.requestResponse
import io.timemates.sdk.authorization.email.requests.StartAuthorizationRequest
import io.timemates.sdk.authorization.email.types.value.VerificationHash
import io.timemates.sdk.common.constructor.createOrThrow
import io.timemates.sdk.common.types.value.Count
import kotlinx.datetime.Instant
import io.timemates.api.rsocket.authorizations.requests.StartAuthorizationRequest as RSocketStartAuthorizationRequest

internal object StartAuthorizationCommand : RSocketCommand<StartAuthorizationRequest, StartAuthorizationRequest.Result> {
override suspend fun execute(
rSocket: RSocket,
input: StartAuthorizationRequest,
): StartAuthorizationRequest.Result {
return rSocket.requestResponse(
route = "authorizations.email.start",
data = RSocketStartAuthorizationRequest(
input.emailAddress.string,
input.metadata.serializable(),
)
).let { result ->
StartAuthorizationRequest.Result(
verificationHash = VerificationHash.createOrThrow(result.verificationHash),
attempts = Count.createOrThrow(result.attempts),
expiresAt = Instant.fromEpochMilliseconds(result.expiresAt),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.timemates.api.rsocket.authorizations.commands

import io.rsocket.kotlin.RSocket
import io.timemates.api.rsocket.authorizations.requests.TerminateAuthorizationRequest
import io.timemates.api.rsocket.common.commands.RSocketCommand
import io.timemates.api.rsocket.common.ext.requestResponse
import io.timemates.sdk.authorization.sessions.requests.TerminateCurrentAuthorizationSessionRequest
import io.timemates.sdk.common.types.Empty

internal object TerminateCurrentAuthorizationCommand : RSocketCommand<TerminateCurrentAuthorizationSessionRequest, Empty> {
override suspend fun execute(rSocket: RSocket, input: TerminateCurrentAuthorizationSessionRequest): Empty {
return rSocket.requestResponse(
route = "authorizations.terminate",
data = TerminateAuthorizationRequest.Current,
accessHash = input.accessHash.string,
).let { _ -> Empty }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.timemates.api.rsocket.authorizations.requests

import io.timemates.api.rsocket.authorizations.types.SerializableAuthorization
import io.timemates.api.rsocket.common.markers.RSocketRequest
import kotlinx.serialization.Serializable

@Serializable
internal data class ConfigureAccountRequest(
val verificationHash: String,
val name: String,
val description: String?,
) : RSocketRequest<ConfigureAccountRequest.Result> {
@Serializable
data class Result(
val authorization: SerializableAuthorization,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.timemates.api.rsocket.authorizations.requests

import io.timemates.api.rsocket.authorizations.types.SerializableAuthorization
import io.timemates.api.rsocket.common.markers.RSocketRequest
import kotlinx.serialization.Serializable

@Serializable
internal data class ConfirmAuthorizationRequest(
val verificationHash: String,
val confirmationCode: String,
) : RSocketRequest<ConfirmAuthorizationRequest.Response> {
data class Response(
val isNewAccount: Boolean,
val authorization: SerializableAuthorization?,
)
}
Loading

0 comments on commit 4d12f6c

Please sign in to comment.