mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-11-14 07:55:13 +00:00
docs: Add Kotlin SDK README with comprehensive API documentation and implement authentication provider with JWT support
- Add README.md with installation instructions, architecture overview, and API examples for all SDK modules - Add AuthProvider class with API key and JWT token support (generateJWTToken, validateJWTToken, getHeaders, clearTokenCache methods) - Add HTTPClient base class with retry logic, error handling, and timeout management - Add GraphQLAPI, PaymentAPI, RatingPlanAPI, SubscriberAPI, System
This commit is contained in:
@@ -0,0 +1,259 @@
|
|||||||
|
# Telecom Platform Kotlin SDK
|
||||||
|
|
||||||
|
Kotlin SDK for the Telecom Platform with coroutines support and type safety.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Modular Architecture**: Separate modules for authentication, HTTP client, and API services
|
||||||
|
- **Coroutines Support**: Full async/await support with Kotlin coroutines
|
||||||
|
- **Type Safety**: Strong typing with Kotlin data classes
|
||||||
|
- **Error Handling**: Comprehensive error handling with sealed classes
|
||||||
|
- **Retry Logic**: Built-in retry mechanism with exponential backoff
|
||||||
|
- **Multiplatform Support**: Works on JVM, Android, and more
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Gradle (Kotlin DSL)
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
dependencies {
|
||||||
|
implementation("com.telecom:telecom-sdk:1.0.0")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gradle (Groovy DSL)
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
dependencies {
|
||||||
|
implementation 'com.telecom:telecom-sdk:1.0.0'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Maven
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.telecom</groupId>
|
||||||
|
<artifactId>telecom-sdk</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The SDK is organized into modular components:
|
||||||
|
|
||||||
|
- `AuthProvider.kt` - Authentication provider handling API keys and JWT tokens
|
||||||
|
- `HTTPClient.kt` - HTTP client with retry logic and authentication
|
||||||
|
- `SubscriberAPI.kt` - Subscriber management API
|
||||||
|
- `UsageAPI.kt` - Usage tracking API
|
||||||
|
- `PaymentAPI.kt` - Payment processing API
|
||||||
|
- `RatingPlanAPI.kt` - Rating plan management API
|
||||||
|
- `SystemAPI.kt` - System monitoring API
|
||||||
|
- `GraphQLAPI.kt` - GraphQL query execution
|
||||||
|
- `TelecomSDK.kt` - Main SDK client integrating all modules
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import com.telecom.TelecomSDK
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
fun main() = runBlocking {
|
||||||
|
val sdk = TelecomSDK(
|
||||||
|
baseURL = "http://localhost:8000",
|
||||||
|
apiKey = "your-api-key"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get subscriber
|
||||||
|
val subscriber = sdk.subscribers.get(1)
|
||||||
|
println("Subscriber: ${subscriber.firstName} ${subscriber.lastName}")
|
||||||
|
|
||||||
|
// List subscribers
|
||||||
|
val subscribers = sdk.subscribers.list(1, 50, "active")
|
||||||
|
println("Total subscribers: ${subscribers.total}")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val config = TelecomConfig(
|
||||||
|
apiURL = "http://localhost:8000",
|
||||||
|
apiKey = "your-api-key",
|
||||||
|
jwtSecret = "your-jwt-secret",
|
||||||
|
timeout = Duration.ofSeconds(30),
|
||||||
|
maxRetries = 3,
|
||||||
|
retryDelay = Duration.ofSeconds(1),
|
||||||
|
enableLogging = false
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subscriber Management
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Get subscriber
|
||||||
|
val subscriber = sdk.subscribers.get(1)
|
||||||
|
|
||||||
|
// List subscribers
|
||||||
|
val subscribers = sdk.subscribers.list(
|
||||||
|
page = 1,
|
||||||
|
pageSize = 50,
|
||||||
|
status = "active"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create subscriber
|
||||||
|
val newSubscriber = sdk.subscribers.create(
|
||||||
|
msisdn = "+1234567890",
|
||||||
|
firstName = "John",
|
||||||
|
lastName = "Doe",
|
||||||
|
email = "[email protected]",
|
||||||
|
planId = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update subscriber
|
||||||
|
val updated = sdk.subscribers.update(
|
||||||
|
id = 1,
|
||||||
|
firstName = "Jane",
|
||||||
|
email = "[email protected]"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete subscriber
|
||||||
|
sdk.subscribers.delete(1)
|
||||||
|
|
||||||
|
// Suspend subscriber
|
||||||
|
sdk.subscribers.suspend(1)
|
||||||
|
|
||||||
|
// Activate subscriber
|
||||||
|
sdk.subscribers.activate(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Management
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
// Get usage stats
|
||||||
|
val start = Instant.now().minus(Duration.ofDays(30))
|
||||||
|
val end = Instant.now()
|
||||||
|
val stats = sdk.usage.getStats(1, start, end)
|
||||||
|
|
||||||
|
// List usage events
|
||||||
|
val events = sdk.usage.listEvents(
|
||||||
|
subscriberId = 1,
|
||||||
|
usageType = "data",
|
||||||
|
page = 1,
|
||||||
|
pageSize = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get real-time usage
|
||||||
|
val realtime = sdk.usage.getRealTime(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Payment Management
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Create payment transaction
|
||||||
|
val transaction = sdk.payments.createTransaction(
|
||||||
|
subscriberId = "1",
|
||||||
|
amount = 25.00,
|
||||||
|
currency = "USD",
|
||||||
|
gateway = "stripe",
|
||||||
|
metadata = mapOf("description" to "Monthly plan")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get transaction
|
||||||
|
val transaction = sdk.payments.getTransaction("txn-123")
|
||||||
|
|
||||||
|
// List transactions
|
||||||
|
val transactions = sdk.payments.listTransactions(
|
||||||
|
subscriberId = 1,
|
||||||
|
status = "completed",
|
||||||
|
page = 1,
|
||||||
|
pageSize = 50
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rating Plans
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// List rating plans
|
||||||
|
val plans = sdk.ratingPlans.list()
|
||||||
|
|
||||||
|
// Get specific plan
|
||||||
|
val plan = sdk.ratingPlans.get("plan-123")
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Management
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Get system stats
|
||||||
|
val stats = sdk.system.getStats()
|
||||||
|
|
||||||
|
// Get health status
|
||||||
|
val health = sdk.system.getHealth()
|
||||||
|
```
|
||||||
|
|
||||||
|
### GraphQL
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Execute GraphQL query
|
||||||
|
val query = """
|
||||||
|
query GetSubscribers($first: Int) {
|
||||||
|
subscribers(first: $first) {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val variables = mapOf("first" to 10)
|
||||||
|
val result = sdk.graphql.execute(query, variables)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
try {
|
||||||
|
val subscriber = sdk.subscribers.get(1)
|
||||||
|
} catch (e: AuthError.JWTSecretNotConfigured) {
|
||||||
|
println("JWT secret not configured")
|
||||||
|
} catch (e: AuthError.InvalidTokenFormat) {
|
||||||
|
println("Invalid token format")
|
||||||
|
} catch (e: AuthError.InvalidTokenSignature) {
|
||||||
|
println("Invalid token signature")
|
||||||
|
} catch (e: AuthError.TokenExpired) {
|
||||||
|
println("Token expired")
|
||||||
|
} catch (e: AuthError.SigningFailed) {
|
||||||
|
println("Signing failed")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Error: ${e.message}")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Kotlin 1.9+
|
||||||
|
- Java 11+
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This SDK is licensed under the MIT License.
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
package com.telecom
|
||||||
|
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.Base64
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication provider for the Telecom SDK
|
||||||
|
*/
|
||||||
|
class AuthProvider(
|
||||||
|
private val apiKey: String? = null,
|
||||||
|
private val jwtSecret: String? = null
|
||||||
|
) {
|
||||||
|
private var tokenCache: String? = null
|
||||||
|
private var tokenExpiry: Long? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get authentication headers for API requests
|
||||||
|
*/
|
||||||
|
fun getHeaders(): Map<String, String> {
|
||||||
|
val headers = mutableMapOf(
|
||||||
|
"Content-Type" to "application/json",
|
||||||
|
"User-Agent" to "Telecom-Kotlin-SDK/1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
apiKey?.let { headers["X-API-Key"] = it }
|
||||||
|
|
||||||
|
if (tokenCache != null && isTokenValid()) {
|
||||||
|
headers["Authorization"] = "Bearer $tokenCache"
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a JWT token for authentication
|
||||||
|
*/
|
||||||
|
fun generateJWTToken(
|
||||||
|
userId: String,
|
||||||
|
expiryHours: Int,
|
||||||
|
additionalClaims: Map<String, Any> = emptyMap()
|
||||||
|
): String {
|
||||||
|
val jwtSecret = jwtSecret ?: throw AuthError.JWTSecretNotConfigured
|
||||||
|
|
||||||
|
val now = System.currentTimeMillis() / 1000
|
||||||
|
val exp = now + (expiryHours * 3600)
|
||||||
|
|
||||||
|
val claims = mutableMapOf<String, Any>(
|
||||||
|
"sub" to userId,
|
||||||
|
"exp" to exp,
|
||||||
|
"iat" to now
|
||||||
|
)
|
||||||
|
claims.putAll(additionalClaims)
|
||||||
|
|
||||||
|
val header = mapOf("alg" to "HS256", "typ" to "JWT")
|
||||||
|
val encodedHeader = base64URLEncode(header)
|
||||||
|
val encodedPayload = base64URLEncode(claims)
|
||||||
|
val signature = sign("$encodedHeader.$encodedPayload", jwtSecret)
|
||||||
|
|
||||||
|
val token = "$encodedHeader.$encodedPayload.$signature"
|
||||||
|
tokenCache = token
|
||||||
|
tokenExpiry = exp
|
||||||
|
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a JWT token
|
||||||
|
*/
|
||||||
|
fun validateJWTToken(token: String): Map<String, Any> {
|
||||||
|
val jwtSecret = jwtSecret ?: throw AuthError.JWTSecretNotConfigured
|
||||||
|
|
||||||
|
val parts = token.split(".")
|
||||||
|
if (parts.size != 3) {
|
||||||
|
throw AuthError.InvalidTokenFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
val (encodedHeader, encodedPayload, signature) = parts
|
||||||
|
val expectedSignature = sign("$encodedHeader.$encodedPayload", jwtSecret)
|
||||||
|
|
||||||
|
if (signature != expectedSignature) {
|
||||||
|
throw AuthError.InvalidTokenSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
val payloadBytes = base64URLDecode(encodedPayload) ?: throw AuthError.FailedToDecodePayload
|
||||||
|
val payload = parseJson(payloadBytes) as? Map<String, Any> ?: throw AuthError.FailedToDecodePayload
|
||||||
|
|
||||||
|
val exp = payload["exp"] as? Number
|
||||||
|
if (exp != null && exp.toLong() < System.currentTimeMillis() / 1000) {
|
||||||
|
throw AuthError.TokenExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cached JWT token
|
||||||
|
*/
|
||||||
|
fun clearTokenCache() {
|
||||||
|
tokenCache = null
|
||||||
|
tokenExpiry = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isTokenValid(): Boolean {
|
||||||
|
if (tokenCache == null || tokenExpiry == null) return false
|
||||||
|
return System.currentTimeMillis() / 1000 < (tokenExpiry ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun base64URLEncode(data: Any): String {
|
||||||
|
val json = toJson(data)
|
||||||
|
return Base64.getUrlEncoder()
|
||||||
|
.withoutPadding()
|
||||||
|
.encodeToString(json.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun base64URLDecode(data: String): ByteArray? {
|
||||||
|
val base64String = data
|
||||||
|
.replace("-", "+")
|
||||||
|
.replace("_", "/")
|
||||||
|
|
||||||
|
val paddingNeeded = (4 - base64String.length % 4) % 4
|
||||||
|
val paddedString = base64String + "=".repeat(paddingNeeded)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
Base64.getUrlDecoder().decode(paddedString)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sign(data: String, secret: String): String {
|
||||||
|
return try {
|
||||||
|
val key = SecretKeySpec(secret.toByteArray(), "HmacSHA256")
|
||||||
|
val mac = Mac.getInstance("HmacSHA256")
|
||||||
|
mac.init(key)
|
||||||
|
val signature = mac.doFinal(data.toByteArray())
|
||||||
|
Base64.getUrlEncoder()
|
||||||
|
.withoutPadding()
|
||||||
|
.encodeToString(signature)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw AuthError.SigningFailed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toJson(data: Any): String {
|
||||||
|
return when (data) {
|
||||||
|
is Map<*, *> -> {
|
||||||
|
val entries = data.entries.joinToString(",") { (k, v) ->
|
||||||
|
"\"${k}\":${toJson(v)}"
|
||||||
|
}
|
||||||
|
"{$entries}"
|
||||||
|
}
|
||||||
|
is List<*> -> {
|
||||||
|
val items = data.joinToString(",") { toJson(it) }
|
||||||
|
"[$items]"
|
||||||
|
}
|
||||||
|
is String -> "\"$data\""
|
||||||
|
is Number -> data.toString()
|
||||||
|
is Boolean -> data.toString()
|
||||||
|
else -> "\"$data\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseJson(json: String): Any? {
|
||||||
|
// JSON parser using kotlinx.serialization
|
||||||
|
// In production, use proper kotlinx.serialization with typed models
|
||||||
|
// For SDK compatibility without external dependencies, using basic parsing
|
||||||
|
return try {
|
||||||
|
when {
|
||||||
|
json.trim().startsWith("{") -> {
|
||||||
|
// Parse JSON object
|
||||||
|
val content = json.trim().removeSurrounding("{", "}")
|
||||||
|
val pairs = content.split(",")
|
||||||
|
val map = mutableMapOf<String, Any>()
|
||||||
|
pairs.forEach { pair ->
|
||||||
|
val (key, value) = pair.split(":", limit = 2)
|
||||||
|
val cleanKey = key.trim().removeSurrounding("\"")
|
||||||
|
val cleanValue = value.trim()
|
||||||
|
map[cleanKey] = when {
|
||||||
|
cleanValue.startsWith("\"") -> cleanValue.removeSurrounding("\"")
|
||||||
|
cleanValue == "true" -> true
|
||||||
|
cleanValue == "false" -> false
|
||||||
|
cleanValue.toIntOrNull() != null -> cleanValue.toInt()
|
||||||
|
cleanValue.toDoubleOrNull() != null -> cleanValue.toDouble()
|
||||||
|
else -> cleanValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
json.trim().startsWith("[") -> {
|
||||||
|
// Parse JSON array
|
||||||
|
val content = json.trim().removeSurrounding("[", "]")
|
||||||
|
if (content.isBlank()) {
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
content.split(",").map { it.trim().removeSurrounding("\"") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> json.trim()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class AuthError(message: String) : Exception(message) {
|
||||||
|
object JWTSecretNotConfigured : AuthError("JWT secret not configured")
|
||||||
|
object InvalidTokenFormat : AuthError("Invalid token format")
|
||||||
|
object InvalidTokenSignature : AuthError("Invalid token signature")
|
||||||
|
object FailedToDecodePayload : AuthError("Failed to decode payload")
|
||||||
|
object TokenExpired : AuthError("Token has expired")
|
||||||
|
object SigningFailed : AuthError("Signing failed")
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.telecom
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for GraphQL queries
|
||||||
|
*/
|
||||||
|
class GraphQLAPI(private val client: HTTPClient) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a GraphQL query
|
||||||
|
*/
|
||||||
|
suspend fun execute(query: String, variables: Map<String, Any>? = null): Map<String, Any> {
|
||||||
|
val request = GraphQLRequest(query, variables)
|
||||||
|
return client.post("/graphql", request)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package com.telecom
|
||||||
|
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.call.*
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP client for making API requests
|
||||||
|
*/
|
||||||
|
class HTTPClient(
|
||||||
|
private val config: TelecomConfig,
|
||||||
|
private val authProvider: AuthProvider
|
||||||
|
) {
|
||||||
|
private val client: HttpClient = HttpClient {
|
||||||
|
defaultRequest {
|
||||||
|
url(config.apiURL)
|
||||||
|
header("User-Agent", "Telecom-Kotlin-SDK/1.0.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
isLenient = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
install(HttpTimeout) {
|
||||||
|
requestTimeoutMillis = config.timeout.toMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an HTTP GET request
|
||||||
|
*/
|
||||||
|
suspend fun <T> get(path: String, params: Map<String, String>? = null): T {
|
||||||
|
return request<T>("GET", path, null, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an HTTP POST request
|
||||||
|
*/
|
||||||
|
suspend fun <T> post(path: String, body: Any? = null): T {
|
||||||
|
return request<T>("POST", path, body, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an HTTP PUT request
|
||||||
|
*/
|
||||||
|
suspend fun <T> put(path: String, body: Any? = null): T {
|
||||||
|
return request<T>("PUT", path, body, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an HTTP DELETE request
|
||||||
|
*/
|
||||||
|
suspend fun delete(path: String) {
|
||||||
|
request<Unit>("DELETE", path, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun <T> request(
|
||||||
|
method: String,
|
||||||
|
path: String,
|
||||||
|
body: Any?,
|
||||||
|
params: Map<String, String>?
|
||||||
|
): T {
|
||||||
|
var lastError: Throwable? = null
|
||||||
|
|
||||||
|
for (attempt in 0..config.maxRetries) {
|
||||||
|
try {
|
||||||
|
val response = client.request(path) {
|
||||||
|
this.method = HttpMethod.parse(method)
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
val headers = authProvider.getHeaders()
|
||||||
|
for ((key, value) in headers) {
|
||||||
|
header(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add query parameters
|
||||||
|
params?.let {
|
||||||
|
it.forEach { (k, v) ->
|
||||||
|
parameter(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add body
|
||||||
|
body?.let {
|
||||||
|
setBody(it)
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResponseErrors(response.status)
|
||||||
|
|
||||||
|
return response.body()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
lastError = e
|
||||||
|
if (attempt < config.maxRetries) {
|
||||||
|
kotlinx.coroutines.delay(config.retryDelay * (1 shl attempt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError ?: NetworkError.RequestFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleResponseErrors(status: HttpStatusCode) {
|
||||||
|
when (status) {
|
||||||
|
HttpStatusCode.Unauthorized -> throw AuthError.AuthenticationFailed
|
||||||
|
HttpStatusCode.TooManyRequests -> throw AuthError.RateLimitExceeded
|
||||||
|
in 400..499 -> throw NetworkError.ClientError(status.value)
|
||||||
|
in 500..599 -> throw NetworkError.ServerError(status.value)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close() {
|
||||||
|
client.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class NetworkError(message: String) : Exception(message) {
|
||||||
|
object RequestFailed : NetworkError("Request failed")
|
||||||
|
class ClientError(code: Int) : NetworkError("Client error: $code")
|
||||||
|
class ServerError(code: Int) : NetworkError("Server error: $code")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend AuthError with additional errors
|
||||||
|
object AuthError {
|
||||||
|
object AuthenticationFailed : AuthError("Authentication failed")
|
||||||
|
object RateLimitExceeded : AuthError("Rate limit exceeded")
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.telecom
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for payment management
|
||||||
|
*/
|
||||||
|
class PaymentAPI(private val client: HTTPClient) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a payment transaction
|
||||||
|
*/
|
||||||
|
suspend fun createTransaction(request: CreatePaymentRequest): PaymentTransaction {
|
||||||
|
return client.post("/v1/payments/transactions", request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a payment transaction by ID
|
||||||
|
*/
|
||||||
|
suspend fun getTransaction(transactionId: String): PaymentTransaction {
|
||||||
|
return client.get("/v1/payments/transactions/$transactionId")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List payment transactions
|
||||||
|
*/
|
||||||
|
suspend fun listTransactions(
|
||||||
|
subscriberId: Long? = null,
|
||||||
|
status: PaymentStatus? = null,
|
||||||
|
page: Int = 1,
|
||||||
|
pageSize: Int = 50
|
||||||
|
): SubscriberList {
|
||||||
|
val params = mutableMapOf(
|
||||||
|
"page" to page.toString(),
|
||||||
|
"page_size" to pageSize.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
subscriberId?.let { params["subscriber_id"] = it.toString() }
|
||||||
|
status?.let { params["status"] = it.toString() }
|
||||||
|
|
||||||
|
return client.get("/v1/payments/transactions", params)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.telecom
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for rating plan management
|
||||||
|
*/
|
||||||
|
class RatingPlanAPI(private val client: HTTPClient) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all available rating plans
|
||||||
|
*/
|
||||||
|
suspend fun list(): List<RatingPlan> {
|
||||||
|
return client.get("/v1/rating-plans")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a rating plan by ID
|
||||||
|
*/
|
||||||
|
suspend fun get(planId: String): RatingPlan {
|
||||||
|
return client.get("/v1/rating-plans/$planId")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.telecom
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for subscriber management
|
||||||
|
*/
|
||||||
|
class SubscriberAPI(private val client: HTTPClient) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get subscriber by ID
|
||||||
|
*/
|
||||||
|
suspend fun get(id: Long): Subscriber {
|
||||||
|
return client.get("/v1/subscribers/$id")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List subscribers with pagination
|
||||||
|
*/
|
||||||
|
suspend fun list(page: Int = 1, pageSize: Int = 50, status: SubscriberStatus? = null): SubscriberList {
|
||||||
|
val params = mutableMapOf(
|
||||||
|
"page" to page.toString(),
|
||||||
|
"page_size" to pageSize.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
status?.let { params["status"] = it.toString() }
|
||||||
|
|
||||||
|
return client.get("/v1/subscribers", params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new subscriber
|
||||||
|
*/
|
||||||
|
suspend fun create(request: CreateSubscriberRequest): Subscriber {
|
||||||
|
return client.post("/v1/subscribers", request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing subscriber
|
||||||
|
*/
|
||||||
|
suspend fun update(id: Long, request: UpdateSubscriberRequest): Subscriber {
|
||||||
|
return client.put("/v1/subscribers/$id", request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a subscriber
|
||||||
|
*/
|
||||||
|
suspend fun delete(id: Long) {
|
||||||
|
client.delete("/v1/subscribers/$id")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspend a subscriber
|
||||||
|
*/
|
||||||
|
suspend fun suspend(id: Long): Subscriber {
|
||||||
|
return client.post("/v1/subscribers/$id/suspend", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate a suspended subscriber
|
||||||
|
*/
|
||||||
|
suspend fun activate(id: Long): Subscriber {
|
||||||
|
return client.post("/v1/subscribers/$id/activate", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.telecom
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for system management
|
||||||
|
*/
|
||||||
|
class SystemAPI(private val client: HTTPClient) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get system statistics
|
||||||
|
*/
|
||||||
|
suspend fun getStats(): SystemStats {
|
||||||
|
return client.get("/v1/system/stats")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get system health status
|
||||||
|
*/
|
||||||
|
suspend fun getHealth(): HealthStatus {
|
||||||
|
return client.get("/v1/health")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,330 +1,83 @@
|
|||||||
package com.telecom
|
package com.telecom
|
||||||
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.call.*
|
|
||||||
import io.ktor.client.plugins.*
|
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import io.ktor.websocket.*
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.serialization.*
|
|
||||||
import kotlinx.serialization.json.*
|
|
||||||
import java.time.*
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Telecom Platform Kotlin SDK
|
* Telecom Platform Kotlin SDK
|
||||||
*
|
*
|
||||||
* Provides coroutine-based access to the Telecom Platform API with full Kotlin serialization support.
|
* Provides coroutine-based access to the Telecom Platform API with full Kotlin serialization support.
|
||||||
*/
|
*/
|
||||||
class TelecomSDK private constructor(
|
class TelecomSDK private constructor(
|
||||||
private val httpClient: HttpClient,
|
|
||||||
private val config: TelecomConfig,
|
private val config: TelecomConfig,
|
||||||
private val webSocketClient: WebSocketClient
|
private val authProvider: AuthProvider,
|
||||||
|
private val httpClient: HTTPClient
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
// API modules
|
||||||
|
val subscribers: SubscriberAPI
|
||||||
|
val usage: UsageAPI
|
||||||
|
val payments: PaymentAPI
|
||||||
|
val ratingPlans: RatingPlanAPI
|
||||||
|
val system: SystemAPI
|
||||||
|
val graphql: GraphQLAPI
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Create a new TelecomSDK instance
|
* Create a new TelecomSDK instance
|
||||||
*/
|
*/
|
||||||
fun create(config: TelecomConfig = TelecomConfig()): TelecomSDK {
|
fun create(config: TelecomConfig = TelecomConfig()): TelecomSDK {
|
||||||
val httpClient = HttpClient {
|
val authProvider = AuthProvider(apiKey = config.apiKey, jwtSecret = config.jwtSecret)
|
||||||
defaultRequest {
|
val httpClient = HTTPClient(config = config, authProvider = authProvider)
|
||||||
url(config.apiURL)
|
|
||||||
header("User-Agent", "Telecom-Kotlin-SDK/1.0.0")
|
|
||||||
config.apiKey?.let { header("Authorization", "Bearer $it") }
|
|
||||||
}
|
|
||||||
|
|
||||||
install(ContentNegotiation) {
|
// Initialize API modules
|
||||||
json(Json {
|
val subscribers = SubscriberAPI(httpClient)
|
||||||
ignoreUnknownKeys = true
|
val usage = UsageAPI(httpClient)
|
||||||
isLenient = true
|
val payments = PaymentAPI(httpClient)
|
||||||
})
|
val ratingPlans = RatingPlanAPI(httpClient)
|
||||||
}
|
val system = SystemAPI(httpClient)
|
||||||
|
val graphql = GraphQLAPI(httpClient)
|
||||||
|
|
||||||
install(HttpTimeout) {
|
return TelecomSDK(config, authProvider, httpClient, subscribers, usage, payments, ratingPlans, system, graphql)
|
||||||
requestTimeoutMillis = config.timeout.toMillis()
|
|
||||||
}
|
|
||||||
|
|
||||||
install(Retry) {
|
|
||||||
maxRetries = config.maxRetries
|
|
||||||
retryOnExceptionIf { request, throwable ->
|
|
||||||
throwable is HttpRequestTimeoutException ||
|
|
||||||
throwable is ConnectTimeoutException
|
|
||||||
}
|
|
||||||
exponentialDelay()
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponseValidator {
|
|
||||||
handleResponseExceptionWithRequest { exception, request ->
|
|
||||||
when (exception) {
|
|
||||||
is ClientRequestException -> {
|
|
||||||
when (exception.response.status) {
|
|
||||||
HttpStatusCode.Unauthorized -> throw TelecomException.AuthenticationError("Authentication failed")
|
|
||||||
HttpStatusCode.TooManyRequests -> throw TelecomException.RateLimitError("Rate limit exceeded")
|
|
||||||
in HttpStatusCode.BadRequest..HttpStatusCode.UnprocessableEntity -> {
|
|
||||||
val errorBody = exception.response.body<String>()
|
|
||||||
throw TelecomException.APIError("API error: $errorBody")
|
|
||||||
}
|
|
||||||
in HttpStatusCode.InternalServerError..HttpStatusCode.NetworkAuthenticationRequired -> {
|
|
||||||
throw TelecomException.ServerError("Server error: ${exception.response.status}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val webSocketClient = WebSocketClient()
|
|
||||||
|
|
||||||
return TelecomSDK(httpClient, config, webSocketClient)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscriber Management
|
private constructor(
|
||||||
|
config: TelecomConfig,
|
||||||
|
authProvider: AuthProvider,
|
||||||
|
httpClient: HTTPClient,
|
||||||
|
subscribers: SubscriberAPI,
|
||||||
|
usage: UsageAPI,
|
||||||
|
payments: PaymentAPI,
|
||||||
|
ratingPlans: RatingPlanAPI,
|
||||||
|
system: SystemAPI,
|
||||||
|
graphql: GraphQLAPI
|
||||||
|
) {
|
||||||
|
this.config = config
|
||||||
|
this.authProvider = authProvider
|
||||||
|
this.httpClient = httpClient
|
||||||
|
this.subscribers = subscribers
|
||||||
|
this.usage = usage
|
||||||
|
this.payments = payments
|
||||||
|
this.ratingPlans = ratingPlans
|
||||||
|
this.system = system
|
||||||
|
this.graphql = graphql
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication methods
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get subscriber by ID
|
* Generate a JWT token for authentication
|
||||||
*/
|
*/
|
||||||
suspend fun getSubscriber(id: Long): Subscriber {
|
fun generateJWTToken(userId: String, expiryHours: Int, additionalClaims: Map<String, Any> = emptyMap()): String {
|
||||||
return httpClient.get("/v1/subscribers/$id").body()
|
return authProvider.generateJWTToken(userId, expiryHours, additionalClaims)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List subscribers with pagination
|
* Validate a JWT token
|
||||||
*/
|
*/
|
||||||
suspend fun listSubscribers(
|
fun validateJWTToken(token: String): Map<String, Any> {
|
||||||
page: Int = 1,
|
return authProvider.validateJWTToken(token)
|
||||||
pageSize: Int = 50,
|
|
||||||
status: SubscriberStatus? = null
|
|
||||||
): SubscriberList {
|
|
||||||
val params = mutableListOf(
|
|
||||||
"page" to page.toString(),
|
|
||||||
"page_size" to pageSize.toString()
|
|
||||||
)
|
|
||||||
status?.let { params.add("status" to it.value) }
|
|
||||||
|
|
||||||
return httpClient.get("/v1/subscribers") {
|
|
||||||
parameter(params)
|
|
||||||
}.body()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Cleanup
|
||||||
* Create a new subscriber
|
|
||||||
*/
|
|
||||||
suspend fun createSubscriber(request: CreateSubscriberRequest): Subscriber {
|
|
||||||
return httpClient.post("/v1/subscribers") {
|
|
||||||
setBody(request)
|
|
||||||
}.body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing subscriber
|
|
||||||
*/
|
|
||||||
suspend fun updateSubscriber(id: Long, request: UpdateSubscriberRequest): Subscriber {
|
|
||||||
return httpClient.put("/v1/subscribers/$id") {
|
|
||||||
setBody(request)
|
|
||||||
}.body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Suspend a subscriber
|
|
||||||
*/
|
|
||||||
suspend fun suspendSubscriber(id: Long): Subscriber {
|
|
||||||
return httpClient.post("/v1/subscribers/$id/suspend").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activate a suspended subscriber
|
|
||||||
*/
|
|
||||||
suspend fun activateSubscriber(id: Long): Subscriber {
|
|
||||||
return httpClient.post("/v1/subscribers/$id/activate").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminate a subscriber
|
|
||||||
*/
|
|
||||||
suspend fun terminateSubscriber(id: Long): Boolean {
|
|
||||||
return httpClient.delete("/v1/subscribers/$id").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage Management
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get usage statistics for a subscriber
|
|
||||||
*/
|
|
||||||
suspend fun getUsageStats(
|
|
||||||
subscriberId: Long,
|
|
||||||
startDate: LocalDateTime,
|
|
||||||
endDate: LocalDateTime
|
|
||||||
): UsageStats {
|
|
||||||
return httpClient.get("/v1/subscribers/$subscriberId/usage") {
|
|
||||||
parameter("start_date", startDate.format(DateTimeFormatter.ISO_DATE_TIME))
|
|
||||||
parameter("end_date", endDate.format(DateTimeFormatter.ISO_DATE_TIME))
|
|
||||||
}.body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List usage events with filtering
|
|
||||||
*/
|
|
||||||
suspend fun listUsageEvents(
|
|
||||||
subscriberId: Long? = null,
|
|
||||||
usageType: UsageType? = null,
|
|
||||||
startDate: LocalDateTime? = null,
|
|
||||||
endDate: LocalDateTime? = null,
|
|
||||||
page: Int = 1,
|
|
||||||
pageSize: Int = 50
|
|
||||||
): UsageEventList {
|
|
||||||
val params = mutableListOf(
|
|
||||||
"page" to page.toString(),
|
|
||||||
"page_size" to pageSize.toString()
|
|
||||||
)
|
|
||||||
|
|
||||||
subscriberId?.let { params.add("subscriber_id" to it.toString()) }
|
|
||||||
usageType?.let { params.add("usage_type" to it.value) }
|
|
||||||
startDate?.let { params.add("start_date", it.format(DateTimeFormatter.ISO_DATE_TIME)) }
|
|
||||||
endDate?.let { params.add("end_date", it.format(DateTimeFormatter.ISO_DATE_TIME)) }
|
|
||||||
|
|
||||||
return httpClient.get("/v1/usage/events") {
|
|
||||||
parameter(params)
|
|
||||||
}.body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get real-time usage for a subscriber
|
|
||||||
*/
|
|
||||||
suspend fun getRealTimeUsage(subscriberId: Long): RealTimeUsage {
|
|
||||||
return httpClient.get("/v1/subscribers/$subscriberId/realtime").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Payment Management
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a payment transaction
|
|
||||||
*/
|
|
||||||
suspend fun createPaymentTransaction(request: CreatePaymentRequest): PaymentTransaction {
|
|
||||||
return httpClient.post("/v1/payments/transactions") {
|
|
||||||
setBody(request)
|
|
||||||
}.body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get payment transaction by ID
|
|
||||||
*/
|
|
||||||
suspend fun getPaymentTransaction(transactionId: String): PaymentTransaction {
|
|
||||||
return httpClient.get("/v1/payments/transactions/$transactionId").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List payment transactions with filtering
|
|
||||||
*/
|
|
||||||
suspend fun listPaymentTransactions(
|
|
||||||
subscriberId: Long? = null,
|
|
||||||
status: PaymentStatus? = null,
|
|
||||||
page: Int = 1,
|
|
||||||
pageSize: Int = 50
|
|
||||||
): PaymentTransactionList {
|
|
||||||
val params = mutableListOf(
|
|
||||||
"page" to page.toString(),
|
|
||||||
"page_size" to pageSize.toString()
|
|
||||||
)
|
|
||||||
|
|
||||||
subscriberId?.let { params.add("subscriber_id" to it.toString()) }
|
|
||||||
status?.let { params.add("status" to it.value) }
|
|
||||||
|
|
||||||
return httpClient.get("/v1/payments/transactions") {
|
|
||||||
parameter(params)
|
|
||||||
}.body()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rating Plans
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all available rating plans
|
|
||||||
*/
|
|
||||||
suspend fun listRatingPlans(): List<RatingPlan> {
|
|
||||||
return httpClient.get("/v1/rating-plans").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get rating plan by ID
|
|
||||||
*/
|
|
||||||
suspend fun getRatingPlan(planId: String): RatingPlan {
|
|
||||||
return httpClient.get("/v1/rating-plans/$planId").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
// System Management
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get system statistics
|
|
||||||
*/
|
|
||||||
suspend fun getSystemStats(): SystemStats {
|
|
||||||
return httpClient.get("/v1/system/stats").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get system health status
|
|
||||||
*/
|
|
||||||
suspend fun getHealthStatus(): HealthStatus {
|
|
||||||
return httpClient.get("/v1/health").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket Support
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to WebSocket for real-time updates
|
|
||||||
*/
|
|
||||||
fun connectWebSocket(
|
|
||||||
messageHandler: suspend (WebSocketMessage) -> Unit
|
|
||||||
): Flow<WebSocketMessage> = flow {
|
|
||||||
val wsUrl = config.apiURL.replace("http://", "ws://") + "/ws"
|
|
||||||
|
|
||||||
webSocketClient.webSocket(
|
|
||||||
urlString = wsUrl,
|
|
||||||
request = {
|
|
||||||
config.apiKey?.let { header("Authorization", "Bearer $it") }
|
|
||||||
}
|
|
||||||
) { session ->
|
|
||||||
session.incoming.consumeAsFlow().collect { frame ->
|
|
||||||
when (frame) {
|
|
||||||
is Frame.Text -> {
|
|
||||||
try {
|
|
||||||
val message = Json.decodeFromString<WebSocketMessage>(frame.readText())
|
|
||||||
emit(message)
|
|
||||||
messageHandler(message)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw TelecomException.WebSocketError("Failed to parse WebSocket message: $e")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// Handle other frame types if needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.catch { e ->
|
|
||||||
throw TelecomException.WebSocketError("WebSocket connection failed: $e")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphQL Support
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a GraphQL query
|
|
||||||
*/
|
|
||||||
suspend fun executeGraphQLQuery(
|
|
||||||
query: String,
|
|
||||||
variables: Map<String, Any> = emptyMap()
|
|
||||||
): GraphQLResponse {
|
|
||||||
val request = GraphQLRequest(query, variables)
|
|
||||||
return httpClient.post("/graphql") {
|
|
||||||
setBody(request)
|
|
||||||
}.body()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the SDK and cleanup resources
|
* Close the SDK and cleanup resources
|
||||||
@@ -340,6 +93,8 @@ class TelecomSDK private constructor(
|
|||||||
data class TelecomConfig(
|
data class TelecomConfig(
|
||||||
val apiURL: String = "http://localhost:8000",
|
val apiURL: String = "http://localhost:8000",
|
||||||
val apiKey: String? = null,
|
val apiKey: String? = null,
|
||||||
val timeout: Duration = Duration.ofSeconds(30),
|
val jwtSecret: String? = null,
|
||||||
val maxRetries: Int = 3
|
val timeout: java.time.Duration = java.time.Duration.ofSeconds(30),
|
||||||
|
val maxRetries: Int = 3,
|
||||||
|
val retryDelay: Long = 1 // in seconds
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.telecom
|
||||||
|
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for usage management
|
||||||
|
*/
|
||||||
|
class UsageAPI(private val client: HTTPClient) {
|
||||||
|
|
||||||
|
private val formatter = DateTimeFormatter.ISO_DATE_TIME
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get usage statistics for a subscriber
|
||||||
|
*/
|
||||||
|
suspend fun getStats(subscriberId: Long, startDate: java.time.LocalDateTime, endDate: java.time.LocalDateTime): UsageStats {
|
||||||
|
val params = mapOf(
|
||||||
|
"start_date" to formatter.format(startDate),
|
||||||
|
"end_date" to formatter.format(endDate)
|
||||||
|
)
|
||||||
|
|
||||||
|
return client.get("/v1/subscribers/$subscriberId/usage", params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List usage events
|
||||||
|
*/
|
||||||
|
suspend fun listEvents(
|
||||||
|
subscriberId: Long? = null,
|
||||||
|
usageType: UsageType? = null,
|
||||||
|
startDate: java.time.LocalDateTime? = null,
|
||||||
|
endDate: java.time.LocalDateTime? = null,
|
||||||
|
page: Int = 1,
|
||||||
|
pageSize: Int = 50
|
||||||
|
): UsageEventList {
|
||||||
|
val params = mutableMapOf(
|
||||||
|
"page" to page.toString(),
|
||||||
|
"page_size" to pageSize.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
subscriberId?.let { params["subscriber_id"] = it.toString() }
|
||||||
|
usageType?.let { params["usage_type"] = it.toString() }
|
||||||
|
startDate?.let { params["start_date"] = formatter.format(it) }
|
||||||
|
endDate?.let { params["end_date"] = formatter.format(it) }
|
||||||
|
|
||||||
|
return client.get("/v1/usage/events", params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get real-time usage for a subscriber
|
||||||
|
*/
|
||||||
|
suspend fun getRealtime(subscriberId: Long): RealTimeUsage {
|
||||||
|
return client.get("/v1/subscribers/$subscriberId/realtime")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user