diff --git a/sdk/kotlin/README.md b/sdk/kotlin/README.md new file mode 100644 index 0000000..7f5ba90 --- /dev/null +++ b/sdk/kotlin/README.md @@ -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 + + com.telecom + telecom-sdk + 1.0.0 + +``` + +## 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 = "john.doe@example.com", + planId = 1 +) + +// Update subscriber +val updated = sdk.subscribers.update( + id = 1, + firstName = "Jane", + email = "jane.doe@example.com" +) + +// 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. diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/AuthProvider.kt b/sdk/kotlin/src/main/kotlin/com/telecom/AuthProvider.kt new file mode 100644 index 0000000..19ca344 --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/com/telecom/AuthProvider.kt @@ -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 { + 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 = emptyMap() + ): String { + val jwtSecret = jwtSecret ?: throw AuthError.JWTSecretNotConfigured + + val now = System.currentTimeMillis() / 1000 + val exp = now + (expiryHours * 3600) + + val claims = mutableMapOf( + "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 { + 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 ?: 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() + 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") +} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/GraphQLAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/GraphQLAPI.kt new file mode 100644 index 0000000..f90414f --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/com/telecom/GraphQLAPI.kt @@ -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? = null): Map { + val request = GraphQLRequest(query, variables) + return client.post("/graphql", request) + } +} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/HTTPClient.kt b/sdk/kotlin/src/main/kotlin/com/telecom/HTTPClient.kt new file mode 100644 index 0000000..43ac043 --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/com/telecom/HTTPClient.kt @@ -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 get(path: String, params: Map? = null): T { + return request("GET", path, null, params) + } + + /** + * Make an HTTP POST request + */ + suspend fun post(path: String, body: Any? = null): T { + return request("POST", path, body, null) + } + + /** + * Make an HTTP PUT request + */ + suspend fun put(path: String, body: Any? = null): T { + return request("PUT", path, body, null) + } + + /** + * Make an HTTP DELETE request + */ + suspend fun delete(path: String) { + request("DELETE", path, null, null) + } + + private suspend fun request( + method: String, + path: String, + body: Any?, + params: Map? + ): 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") +} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/PaymentAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/PaymentAPI.kt new file mode 100644 index 0000000..ce5f9d3 --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/com/telecom/PaymentAPI.kt @@ -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) + } +} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/RatingPlanAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/RatingPlanAPI.kt new file mode 100644 index 0000000..a3991d4 --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/com/telecom/RatingPlanAPI.kt @@ -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 { + 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") + } +} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/SubscriberAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/SubscriberAPI.kt new file mode 100644 index 0000000..b025387 --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/com/telecom/SubscriberAPI.kt @@ -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) + } +} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/SystemAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/SystemAPI.kt new file mode 100644 index 0000000..9bdb103 --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/com/telecom/SystemAPI.kt @@ -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") + } +} diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/TelecomSDK.kt b/sdk/kotlin/src/main/kotlin/com/telecom/TelecomSDK.kt index fe1aa88..22e9f67 100644 --- a/sdk/kotlin/src/main/kotlin/com/telecom/TelecomSDK.kt +++ b/sdk/kotlin/src/main/kotlin/com/telecom/TelecomSDK.kt @@ -1,330 +1,83 @@ 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 * * Provides coroutine-based access to the Telecom Platform API with full Kotlin serialization support. */ class TelecomSDK private constructor( - private val httpClient: HttpClient, 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 { /** * Create a new TelecomSDK instance */ fun create(config: TelecomConfig = TelecomConfig()): TelecomSDK { - val httpClient = HttpClient { - defaultRequest { - url(config.apiURL) - header("User-Agent", "Telecom-Kotlin-SDK/1.0.0") - config.apiKey?.let { header("Authorization", "Bearer $it") } - } - - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - isLenient = true - }) - } - - install(HttpTimeout) { - 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() - throw TelecomException.APIError("API error: $errorBody") - } - in HttpStatusCode.InternalServerError..HttpStatusCode.NetworkAuthenticationRequired -> { - throw TelecomException.ServerError("Server error: ${exception.response.status}") - } - } - } - } - } - } - } + val authProvider = AuthProvider(apiKey = config.apiKey, jwtSecret = config.jwtSecret) + val httpClient = HTTPClient(config = config, authProvider = authProvider) - val webSocketClient = WebSocketClient() + // Initialize API modules + val subscribers = SubscriberAPI(httpClient) + val usage = UsageAPI(httpClient) + val payments = PaymentAPI(httpClient) + val ratingPlans = RatingPlanAPI(httpClient) + val system = SystemAPI(httpClient) + val graphql = GraphQLAPI(httpClient) - return TelecomSDK(httpClient, config, webSocketClient) + return TelecomSDK(config, authProvider, httpClient, subscribers, usage, payments, ratingPlans, system, graphql) } } - // 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 { - return httpClient.get("/v1/subscribers/$id").body() + fun generateJWTToken(userId: String, expiryHours: Int, additionalClaims: Map = emptyMap()): String { + return authProvider.generateJWTToken(userId, expiryHours, additionalClaims) } /** - * List subscribers with pagination + * Validate a JWT token */ - suspend fun listSubscribers( - page: Int = 1, - 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() + fun validateJWTToken(token: String): Map { + return authProvider.validateJWTToken(token) } - /** - * 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 { - 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 = 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(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 = emptyMap() - ): GraphQLResponse { - val request = GraphQLRequest(query, variables) - return httpClient.post("/graphql") { - setBody(request) - }.body() - } + // Cleanup /** * Close the SDK and cleanup resources @@ -340,6 +93,8 @@ class TelecomSDK private constructor( data class TelecomConfig( val apiURL: String = "http://localhost:8000", val apiKey: String? = null, - val timeout: Duration = Duration.ofSeconds(30), - val maxRetries: Int = 3 + val jwtSecret: String? = null, + val timeout: java.time.Duration = java.time.Duration.ofSeconds(30), + val maxRetries: Int = 3, + val retryDelay: Long = 1 // in seconds ) diff --git a/sdk/kotlin/src/main/kotlin/com/telecom/UsageAPI.kt b/sdk/kotlin/src/main/kotlin/com/telecom/UsageAPI.kt new file mode 100644 index 0000000..10aeca5 --- /dev/null +++ b/sdk/kotlin/src/main/kotlin/com/telecom/UsageAPI.kt @@ -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") + } +}