From f6590462f5e382fde4f27dc03813474670c3640b Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sat, 25 Apr 2026 04:53:22 +0300 Subject: [PATCH] docs: Add Swift 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, SystemAPI, and UsageAPI modules - Add TelecomSDK main client integrating all API --- sdk/swift/README.md | 249 +++++++++++++++ .../Sources/TelecomSDK/AuthProvider.swift | 165 ++++++++++ sdk/swift/Sources/TelecomSDK/GraphQLAPI.swift | 16 + sdk/swift/Sources/TelecomSDK/HTTPClient.swift | 131 ++++++++ sdk/swift/Sources/TelecomSDK/PaymentAPI.swift | 42 +++ .../Sources/TelecomSDK/RatingPlanAPI.swift | 20 ++ .../Sources/TelecomSDK/SubscriberAPI.swift | 56 ++++ sdk/swift/Sources/TelecomSDK/SystemAPI.swift | 20 ++ sdk/swift/Sources/TelecomSDK/TelecomSDK.swift | 283 +++--------------- sdk/swift/Sources/TelecomSDK/UsageAPI.swift | 58 ++++ 10 files changed, 796 insertions(+), 244 deletions(-) create mode 100644 sdk/swift/README.md create mode 100644 sdk/swift/Sources/TelecomSDK/AuthProvider.swift create mode 100644 sdk/swift/Sources/TelecomSDK/GraphQLAPI.swift create mode 100644 sdk/swift/Sources/TelecomSDK/HTTPClient.swift create mode 100644 sdk/swift/Sources/TelecomSDK/PaymentAPI.swift create mode 100644 sdk/swift/Sources/TelecomSDK/RatingPlanAPI.swift create mode 100644 sdk/swift/Sources/TelecomSDK/SubscriberAPI.swift create mode 100644 sdk/swift/Sources/TelecomSDK/SystemAPI.swift create mode 100644 sdk/swift/Sources/TelecomSDK/UsageAPI.swift diff --git a/sdk/swift/README.md b/sdk/swift/README.md new file mode 100644 index 0000000..2cc165c --- /dev/null +++ b/sdk/swift/README.md @@ -0,0 +1,249 @@ +# Telecom Platform Swift SDK + +Swift SDK for the Telecom Platform with async/await support and type safety. + +## Features + +- **Modular Architecture**: Separate modules for authentication, HTTP client, and API services +- **Async/Await Support**: Full async/await support with Swift Concurrency +- **Type Safety**: Strong typing with Swift structs and enums +- **Error Handling**: Comprehensive error handling with custom error types +- **Retry Logic**: Built-in retry mechanism with exponential backoff +- **iOS & macOS Support**: Universal framework for Apple platforms + +## Installation + +### Swift Package Manager + +Add to your `Package.swift`: + +```swift +dependencies: [ + .package( + url: "https://github.com/nutcas3/telecom-sdk-swift", + from: "1.0.0" + ) +] +``` + +### Manual Installation + +```bash +cd sdk/swift +swift build +``` + +## Architecture + +The SDK is organized into modular components: + +- `AuthProvider.swift` - Authentication provider handling API keys and JWT tokens +- `HTTPClient.swift` - HTTP client with retry logic and authentication +- `SubscriberAPI.swift` - Subscriber management API +- `UsageAPI.swift` - Usage tracking API +- `PaymentAPI.swift` - Payment processing API +- `RatingPlanAPI.swift` - Rating plan management API +- `SystemAPI.swift` - System monitoring API +- `GraphQLAPI.swift` - GraphQL query execution +- `TelecomSDK.swift` - Main SDK client integrating all modules + +## Quick Start + +```swift +import TelecomSDK + +let sdk = try TelecomSDK( + baseURL: "http://localhost:8000", + apiKey: "your-api-key" +) + +// Get subscriber +let subscriber = try await sdk.subscribers.get(id: 1) +print("Subscriber: \(subscriber.firstName) \(subscriber.lastName)") + +// List subscribers +let subscribers = try await sdk.subscribers.list(page: 1, pageSize: 50, status: "active") +print("Total subscribers: \(subscribers.total)") +``` + +## API Reference + +### Configuration + +```swift +let config = TelecomConfig( + baseURL: "http://localhost:8000", + apiKey: "your-api-key", + jwtSecret: "your-jwt-secret", + timeout: 30.0, + maxRetries: 3, + retryDelay: 1.0, + enableLogging: false +) +``` + +### Subscriber Management + +```swift +// Get subscriber +let subscriber = try await sdk.subscribers.get(id: 1) + +// List subscribers +let subscribers = try await sdk.subscribers.list( + page: 1, + pageSize: 50, + status: "active" +) + +// Create subscriber +let newSubscriber = try await sdk.subscribers.create( + msisdn: "+1234567890", + firstName: "John", + lastName: "Doe", + email: "john.doe@example.com", + planId: 1 +) + +// Update subscriber +let updated = try await sdk.subscribers.update( + id: 1, + firstName: "Jane", + email: "jane.doe@example.com" +) + +// Delete subscriber +try await sdk.subscribers.delete(id: 1) + +// Suspend subscriber +try await sdk.subscribers.suspend(id: 1) + +// Activate subscriber +try await sdk.subscribers.activate(id: 1) +``` + +### Usage Management + +```swift +import Foundation + +// Get usage stats +let start = Date().addingTimeInterval(-30 * 24 * 60 * 60) +let end = Date() +let stats = try await sdk.usage.getStats( + subscriberId: 1, + startDate: start, + endDate: end +) + +// List usage events +let events = try await sdk.usage.listEvents( + subscriberId: 1, + usageType: "data", + page: 1, + pageSize: 50 +) + +// Get real-time usage +let realtime = try await sdk.usage.getRealTime(subscriberId: 1) +``` + +### Payment Management + +```swift +// Create payment transaction +let transaction = try await sdk.payments.createTransaction( + subscriberId: 1, + amount: 25.00, + currency: "USD", + gateway: "stripe", + metadata: ["description": "Monthly plan"] +) + +// Get transaction +let transaction = try await sdk.payments.getTransaction(id: "txn-123") + +// List transactions +let transactions = try await sdk.payments.listTransactions( + subscriberId: 1, + status: "completed", + page: 1, + pageSize: 50 +) +``` + +### Rating Plans + +```swift +// List rating plans +let plans = try await sdk.ratingPlans.list() + +// Get specific plan +let plan = try await sdk.ratingPlans.get(id: "plan-123") +``` + +### System Management + +```swift +// Get system stats +let stats = try await sdk.system.getStats() + +// Get health status +let health = try await sdk.system.getHealth() +``` + +### GraphQL + +```swift +// Execute GraphQL query +let query = """ +query GetSubscribers($first: Int) { + subscribers(first: $first) { + id + firstName + lastName + } +} +""" + +let variables = ["first": 10] +let result = try await sdk.graphql.execute(query, variables: variables) +``` + +## Error Handling + +```swift +do { + let subscriber = try await sdk.subscribers.get(id: 1) +} catch TelecomError.authenticationError { + print("Authentication failed") +} catch TelecomError.apiError(let message) { + print("API error: \(message)") +} catch TelecomError.networkError(let message) { + print("Network error: \(message)") +} catch { + print("Error: \(error)") +} +``` + +## Development + +### Building + +```bash +swift build +``` + +### Testing + +```bash +swift test +``` + +## Requirements + +- iOS 13.0+ / macOS 10.15+ +- Swift 5.5+ + +## License + +This SDK is licensed under the MIT License. diff --git a/sdk/swift/Sources/TelecomSDK/AuthProvider.swift b/sdk/swift/Sources/TelecomSDK/AuthProvider.swift new file mode 100644 index 0000000..3e41ebb --- /dev/null +++ b/sdk/swift/Sources/TelecomSDK/AuthProvider.swift @@ -0,0 +1,165 @@ +import Foundation +import CryptoKit + +/// Authentication provider for the Telecom SDK +public class AuthProvider { + private var apiKey: String? + private var jwtSecret: String? + private var tokenCache: String? + private var tokenExpiry: Date? + + public init(apiKey: String? = nil, jwtSecret: String? = nil) { + self.apiKey = apiKey + self.jwtSecret = jwtSecret + } + + /// Get authentication headers for API requests + public func getHeaders() -> [String: String] { + var headers: [String: String] = [ + "Content-Type": "application/json", + "User-Agent": "Telecom-Swift-SDK/1.0.0" + ] + + if let apiKey = apiKey { + headers["X-API-Key"] = apiKey + } + + if let token = tokenCache, isTokenValid() { + headers["Authorization"] = "Bearer \(token)" + } + + return headers + } + + /// Generate a JWT token for authentication + public func generateJWTToken(userID: String, expiryHours: Int, additionalClaims: [String: Any] = [:]) throws -> String { + guard let jwtSecret = jwtSecret else { + throw AuthError.jwtSecretNotConfigured + } + + let now = Date() + let exp = now.addingTimeInterval(TimeInterval(expiryHours * 3600)) + + var claims: [String: Any] = [ + "sub": userID, + "exp": Int(exp.timeIntervalSince1970), + "iat": Int(now.timeIntervalSince1970) + ] + + for (key, value) in additionalClaims { + claims[key] = value + } + + let header: [String: Any] = [ + "alg": "HS256", + "typ": "JWT" + ] + + let encodedHeader = base64URLEncode(header) + let encodedPayload = base64URLEncode(claims) + let signature = sign("\(encodedHeader).\(encodedPayload)", secret: jwtSecret) + + let token = "\(encodedHeader).\(encodedPayload).\(signature)" + tokenCache = token + tokenExpiry = exp + + return token + } + + /// Validate a JWT token + public func validateJWTToken(_ token: String) throws -> [String: Any] { + guard let jwtSecret = jwtSecret else { + throw AuthError.jwtSecretNotConfigured + } + + let parts = token.split(separator: ".", maxSplits: 2) + guard parts.count == 3 else { + throw AuthError.invalidTokenFormat + } + + let encodedHeader = String(parts[0]) + let encodedPayload = String(parts[1]) + let signature = String(parts[2]) + + let expectedSignature = try sign("\(encodedHeader).\(encodedPayload)", secret: jwtSecret) + + guard signature == expectedSignature else { + throw AuthError.invalidTokenSignature + } + + guard let payloadData = base64URLDecode(encodedPayload) else { + throw AuthError.failedToDecodePayload + } + + guard let payload = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any] else { + throw AuthError.failedToDecodePayload + } + + if let exp = payload["exp"] as? Int { + if Date().timeIntervalSince1970 > Double(exp) { + throw AuthError.tokenExpired + } + } + + return payload + } + + /// Clear the cached JWT token + public func clearTokenCache() { + tokenCache = nil + tokenExpiry = nil + } + + private func isTokenValid() -> Bool { + guard let tokenCache = tokenCache, let tokenExpiry = tokenExpiry else { + return false + } + return Date() < tokenExpiry + } + + private func base64URLEncode(_ data: Any) -> String { + guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else { + return "" + } + return jsonData.base64EncodedString() + .replacingOccurrences(of: "=", with: "") + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + } + + private func base64URLDecode(_ data: String) -> Data? { + var base64String = data + .replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + + while base64String.count % 4 != 0 { + base64String += "=" + } + + return Data(base64Encoded: base64String) + } + + private func sign(_ data: String, secret: String) throws -> String { + guard let secretData = secret.data(using: .utf8), + let dataData = data.data(using: .utf8) else { + throw AuthError.signingFailed + } + + let key = SymmetricKey(data: secretData) + let signature = HMAC.authenticationCode(for: dataData, using: key) + + return Data(signature).base64EncodedString() + .replacingOccurrences(of: "=", with: "") + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + } +} + +public enum AuthError: Error { + case jwtSecretNotConfigured + case invalidTokenFormat + case invalidTokenSignature + case failedToDecodePayload + case tokenExpired + case signingFailed +} diff --git a/sdk/swift/Sources/TelecomSDK/GraphQLAPI.swift b/sdk/swift/Sources/TelecomSDK/GraphQLAPI.swift new file mode 100644 index 0000000..a2338c2 --- /dev/null +++ b/sdk/swift/Sources/TelecomSDK/GraphQLAPI.swift @@ -0,0 +1,16 @@ +import Foundation + +/// API for GraphQL queries +public class GraphQLAPI { + private let client: HTTPClient + + public init(client: HTTPClient) { + self.client = client + } + + /// Execute a GraphQL query + public func execute(query: String, variables: [String: Any]? = nil) async throws -> [String: Any] { + let request = GraphQLRequest(query: query, variables: variables) + return try await client.post(path: "/graphql", body: request) + } +} diff --git a/sdk/swift/Sources/TelecomSDK/HTTPClient.swift b/sdk/swift/Sources/TelecomSDK/HTTPClient.swift new file mode 100644 index 0000000..efdcd97 --- /dev/null +++ b/sdk/swift/Sources/TelecomSDK/HTTPClient.swift @@ -0,0 +1,131 @@ +import Foundation +import NIOHTTP1 + +/// HTTP client for making API requests +public class HTTPClient { + private let config: TelecomConfig + private let authProvider: AuthProvider + private let session: URLSession + + public init(config: TelecomConfig, authProvider: AuthProvider) { + self.config = config + self.authProvider = authProvider + + let configuration = URLSessionConfiguration.default + configuration.timeoutIntervalForRequest = config.timeout + self.session = URLSession(configuration: configuration) + } + + /// Make an HTTP GET request + public func get(path: String, params: [String: String]? = nil) async throws -> T { + return try await request("GET", path: path, body: nil, params: params) + } + + /// Make an HTTP POST request + public func post(path: String, body: Encodable? = nil) async throws -> T { + return try await request("POST", path: path, body: body, params: nil) + } + + /// Make an HTTP PUT request + public func put(path: String, body: Encodable? = nil) async throws -> T { + return try await request("PUT", path: path, body: body, params: nil) + } + + /// Make an HTTP DELETE request + public func delete(path: String) async throws { + let _: EmptyResponse = try await request("DELETE", path: path, body: nil, params: nil) + } + + private func request( + _ method: String, + path: String, + body: Encodable?, + params: [String: String]? + ) async throws -> T { + var components = URLComponents(string: config.baseURL) + components?.path = path + + if let params = params { + components?.queryItems = params.map { URLQueryItem(name: $0.key, value: $0.value) } + } + + guard let url = components?.url else { + throw NetworkError.invalidURL + } + + var request = URLRequest(url: url) + request.httpMethod = method + + // Set headers + let headers = authProvider.getHeaders() + for (key, value) in headers { + request.setValue(value, forHTTPHeaderField: key) + } + + // Add body + if let body = body { + request.httpBody = try JSONEncoder().encode(body) + } + + // Retry logic + var lastError: Error? + for attempt in 0...config.maxRetries { + do { + let (data, response) = try await session.data(for: request) + + guard let httpResponse = response as? HTTPURLResponse else { + throw NetworkError.invalidResponse + } + + try handleResponseErrors(httpResponse) + + if T.self == EmptyResponse.self { + return EmptyResponse() as! T + } + + return try JSONDecoder().decode(T.self, from: data) + } catch { + lastError = error + if attempt < config.maxRetries { + try await Task.sleep(nanoseconds: UInt64(config.retryDelay * 1_000_000_000 * pow(2.0, Double(attempt)))) + } + } + } + + throw lastError ?? NetworkError.requestFailed + } + + private func handleResponseErrors(_ response: HTTPURLResponse) throws { + switch response.statusCode { + case 401: + throw AuthError.authenticationFailed + case 429: + throw AuthError.rateLimitExceeded + case 400..<500: + throw NetworkError.clientError(response.statusCode) + case 500..<600: + throw NetworkError.serverError(response.statusCode) + default: + break + } + } + + public func close() { + session.finishTasksAndInvalidate() + } +} + +private struct EmptyResponse: Decodable {} + +public enum NetworkError: Error { + case invalidURL + case invalidResponse + case requestFailed + case clientError(Int) + case serverError(Int) +} + +extension AuthError { + static let authenticationFailed = AuthError.jwtSecretNotConfigured + static let rateLimitExceeded = AuthError.tokenExpired +} diff --git a/sdk/swift/Sources/TelecomSDK/PaymentAPI.swift b/sdk/swift/Sources/TelecomSDK/PaymentAPI.swift new file mode 100644 index 0000000..6447a2c --- /dev/null +++ b/sdk/swift/Sources/TelecomSDK/PaymentAPI.swift @@ -0,0 +1,42 @@ +import Foundation + +/// API for payment management +public class PaymentAPI { + private let client: HTTPClient + + public init(client: HTTPClient) { + self.client = client + } + + /// Create a payment transaction + public func createTransaction(_ request: CreatePaymentRequest) async throws -> PaymentTransaction { + return try await client.post(path: "/v1/payments/transactions", body: request) + } + + /// Get a payment transaction by ID + public func getTransaction(transactionID: String) async throws -> PaymentTransaction { + return try await client.get(path: "/v1/payments/transactions/\(transactionID)") + } + + /// List payment transactions + public func listTransactions( + subscriberID: Int64? = nil, + status: PaymentStatus? = nil, + page: Int = 1, + pageSize: Int = 50 + ) async throws -> SubscriberList { + var params: [String: String] = [ + "page": "\(page)", + "page_size": "\(pageSize)" + ] + + if let subscriberID = subscriberID { + params["subscriber_id"] = "\(subscriberID)" + } + if let status = status { + params["status"] = "\(status)" + } + + return try await client.get(path: "/v1/payments/transactions", params: params) + } +} diff --git a/sdk/swift/Sources/TelecomSDK/RatingPlanAPI.swift b/sdk/swift/Sources/TelecomSDK/RatingPlanAPI.swift new file mode 100644 index 0000000..b48e47f --- /dev/null +++ b/sdk/swift/Sources/TelecomSDK/RatingPlanAPI.swift @@ -0,0 +1,20 @@ +import Foundation + +/// API for rating plan management +public class RatingPlanAPI { + private let client: HTTPClient + + public init(client: HTTPClient) { + self.client = client + } + + /// List all available rating plans + public func list() async throws -> [RatingPlan] { + return try await client.get(path: "/v1/rating-plans") + } + + /// Get a rating plan by ID + public func get(planID: String) async throws -> RatingPlan { + return try await client.get(path: "/v1/rating-plans/\(planID)") + } +} diff --git a/sdk/swift/Sources/TelecomSDK/SubscriberAPI.swift b/sdk/swift/Sources/TelecomSDK/SubscriberAPI.swift new file mode 100644 index 0000000..368fd81 --- /dev/null +++ b/sdk/swift/Sources/TelecomSDK/SubscriberAPI.swift @@ -0,0 +1,56 @@ +import Foundation + +/// API for subscriber management +public class SubscriberAPI { + private let client: HTTPClient + + public init(client: HTTPClient) { + self.client = client + } + + /// Get subscriber by ID + public func get(id: Int64) async throws -> Subscriber { + return try await client.get(path: "/v1/subscribers/\(id)") + } + + /// List subscribers with pagination + public func list(page: Int = 1, pageSize: Int = 50, status: SubscriberStatus? = nil) async throws -> SubscriberList { + var params: [String: String] = [ + "page": "\(page)", + "page_size": "\(pageSize)" + ] + + if let status = status { + params["status"] = "\(status)" + } + + return try await client.get(path: "/v1/subscribers", params: params) + } + + /// Create a new subscriber + public func create(_ request: CreateSubscriberRequest) async throws -> Subscriber { + return try await client.post(path: "/v1/subscribers", body: request) + } + + /// Update an existing subscriber + public func update(id: Int64, request: UpdateSubscriberRequest) async throws -> Subscriber { + return try await client.put(path: "/v1/subscribers/\(id)", body: request) + } + + /// Delete a subscriber + public func delete(id: Int64) async throws { + try await client.delete(path: "/v1/subscribers/\(id)") + } + + /// Suspend a subscriber + public func suspend(id: Int64) async throws -> Subscriber { + return try await client.post(path: "/v1/subscribers/\(id)/suspend", body: nil as EmptyRequest?) + } + + /// Activate a suspended subscriber + public func activate(id: Int64) async throws -> Subscriber { + return try await client.post(path: "/v1/subscribers/\(id)/activate", body: nil as EmptyRequest?) + } +} + +private struct EmptyRequest: Encodable {} diff --git a/sdk/swift/Sources/TelecomSDK/SystemAPI.swift b/sdk/swift/Sources/TelecomSDK/SystemAPI.swift new file mode 100644 index 0000000..44a3ed0 --- /dev/null +++ b/sdk/swift/Sources/TelecomSDK/SystemAPI.swift @@ -0,0 +1,20 @@ +import Foundation + +/// API for system management +public class SystemAPI { + private let client: HTTPClient + + public init(client: HTTPClient) { + self.client = client + } + + /// Get system statistics + public func getStats() async throws -> SystemStats { + return try await client.get(path: "/v1/system/stats") + } + + /// Get system health status + public func getHealth() async throws -> HealthStatus { + return try await client.get(path: "/v1/health") + } +} diff --git a/sdk/swift/Sources/TelecomSDK/TelecomSDK.swift b/sdk/swift/Sources/TelecomSDK/TelecomSDK.swift index 54c1deb..6270367 100644 --- a/sdk/swift/Sources/TelecomSDK/TelecomSDK.swift +++ b/sdk/swift/Sources/TelecomSDK/TelecomSDK.swift @@ -10,8 +10,16 @@ public class TelecomSDK { // MARK: - Properties private let config: TelecomConfig + private let authProvider: AuthProvider private let httpClient: HTTPClient - private let webSocketClient: WebSocketClient + + // API modules + public let subscribers: SubscriberAPI + public let usage: UsageAPI + public let payments: PaymentAPI + public let ratingPlans: RatingPlanAPI + public let system: SystemAPI + public let graphql: GraphQLAPI // MARK: - Initialization @@ -19,256 +27,35 @@ public class TelecomSDK { /// - Parameter config: Configuration for the SDK public init(config: TelecomConfig = TelecomConfig()) { self.config = config - self.httpClient = HTTPClient(config: config) - self.webSocketClient = WebSocketClient(config: config) - } - - // MARK: - Subscriber Management - - /// Get subscriber by ID - /// - Parameter id: Subscriber ID - /// - Returns: Subscriber information - public func getSubscriber(id: Int64) async throws -> Subscriber { - return try await httpClient.get(path: "/v1/subscribers/\(id)") - } - - /// List subscribers with pagination - /// - Parameters: - /// - page: Page number - /// - pageSize: Page size - /// - status: Optional status filter - /// - Returns: Paginated list of subscribers - public func listSubscribers( - page: Int = 1, - pageSize: Int = 50, - status: SubscriberStatus? = nil - ) async throws -> SubscriberList { - var queryParams: [String: String] = [ - "page": "\(page)", - "page_size": "\(pageSize)" - ] + self.authProvider = AuthProvider(apiKey: config.apiKey, jwtSecret: config.jwtSecret) + self.httpClient = HTTPClient(config: config, authProvider: authProvider) - if let status = status { - queryParams["status"] = status.rawValue - } - - return try await httpClient.get(path: "/v1/subscribers", queryParams: queryParams) + // Initialize API modules + self.subscribers = SubscriberAPI(client: httpClient) + self.usage = UsageAPI(client: httpClient) + self.payments = PaymentAPI(client: httpClient) + self.ratingPlans = RatingPlanAPI(client: httpClient) + self.system = SystemAPI(client: httpClient) + self.graphql = GraphQLAPI(client: httpClient) } - /// Create a new subscriber - /// - Parameter request: Create subscriber request - /// - Returns: Created subscriber - public func createSubscriber(request: CreateSubscriberRequest) async throws -> Subscriber { - return try await httpClient.post(path: "/v1/subscribers", body: request) + // MARK: - Authentication Methods + + /// Generate a JWT token for authentication + public func generateJWTToken(userID: String, expiryHours: Int, additionalClaims: [String: Any] = [:]) throws -> String { + return try authProvider.generateJWTToken(userID: userID, expiryHours: expiryHours, additionalClaims: additionalClaims) } - /// Update an existing subscriber - /// - Parameters: - /// - id: Subscriber ID - /// - request: Update subscriber request - /// - Returns: Updated subscriber - public func updateSubscriber(id: Int64, request: UpdateSubscriberRequest) async throws -> Subscriber { - return try await httpClient.put(path: "/v1/subscribers/\(id)", body: request) + /// Validate a JWT token + public func validateJWTToken(_ token: String) throws -> [String: Any] { + return try authProvider.validateJWTToken(token) } - /// Suspend a subscriber - /// - Parameter id: Subscriber ID - /// - Returns: Suspended subscriber - public func suspendSubscriber(id: Int64) async throws -> Subscriber { - return try await httpClient.post(path: "/v1/subscribers/\(id)/suspend") - } + // MARK: - Cleanup - /// Activate a suspended subscriber - /// - Parameter id: Subscriber ID - /// - Returns: Activated subscriber - public func activateSubscriber(id: Int64) async throws -> Subscriber { - return try await httpClient.post(path: "/v1/subscribers/\(id)/activate") - } - - /// Terminate a subscriber - /// - Parameter id: Subscriber ID - /// - Returns: Success status - public func terminateSubscriber(id: Int64) async throws -> Bool { - return try await httpClient.delete(path: "/v1/subscribers/\(id)") - } - - // MARK: - Usage Management - - /// Get usage statistics for a subscriber - /// - Parameters: - /// - subscriberId: Subscriber ID - /// - startDate: Start date - /// - endDate: End date - /// - Returns: Usage statistics - public func getUsageStats( - subscriberId: Int64, - startDate: Date, - endDate: Date - ) async throws -> UsageStats { - let formatter = ISO8601DateFormatter() - formatter.timeZone = TimeZone.current - - let queryParams: [String: String] = [ - "start_date": formatter.string(from: startDate), - "end_date": formatter.string(from: endDate) - ] - - return try await httpClient.get( - path: "/v1/subscribers/\(subscriberId)/usage", - queryParams: queryParams - ) - } - - /// List usage events with filtering - /// - Parameters: - /// - subscriberId: Optional subscriber ID filter - /// - usageType: Optional usage type filter - /// - startDate: Optional start date filter - /// - endDate: Optional end date filter - /// - page: Page number - /// - pageSize: Page size - /// - Returns: Paginated list of usage events - public func listUsageEvents( - subscriberId: Int64? = nil, - usageType: UsageType? = nil, - startDate: Date? = nil, - endDate: Date? = nil, - page: Int = 1, - pageSize: Int = 50 - ) async throws -> UsageEventList { - var queryParams: [String: String] = [ - "page": "\(page)", - "page_size": "\(pageSize)" - ] - - if let subscriberId = subscriberId { - queryParams["subscriber_id"] = "\(subscriberId)" - } - - if let usageType = usageType { - queryParams["usage_type"] = usageType.rawValue - } - - if let startDate = startDate { - let formatter = ISO8601DateFormatter() - formatter.timeZone = TimeZone.current - queryParams["start_date"] = formatter.string(from: startDate) - } - - if let endDate = endDate { - let formatter = ISO8601DateFormatter() - formatter.timeZone = TimeZone.current - queryParams["end_date"] = formatter.string(from: endDate) - } - - return try await httpClient.get(path: "/v1/usage/events", queryParams: queryParams) - } - - /// Get real-time usage for a subscriber - /// - Parameter subscriberId: Subscriber ID - /// - Returns: Real-time usage data - public func getRealTimeUsage(subscriberId: Int64) async throws -> RealTimeUsage { - return try await httpClient.get(path: "/v1/subscribers/\(subscriberId)/realtime") - } - - // MARK: - Payment Management - - /// Create a payment transaction - /// - Parameter request: Create payment request - /// - Returns: Created payment transaction - public func createPaymentTransaction(request: CreatePaymentRequest) async throws -> PaymentTransaction { - return try await httpClient.post(path: "/v1/payments/transactions", body: request) - } - - /// Get payment transaction by ID - /// - Parameter transactionId: Transaction ID - /// - Returns: Payment transaction - public func getPaymentTransaction(transactionId: String) async throws -> PaymentTransaction { - return try await httpClient.get(path: "/v1/payments/transactions/\(transactionId)") - } - - /// List payment transactions with filtering - /// - Parameters: - /// - subscriberId: Optional subscriber ID filter - /// - status: Optional status filter - /// - page: Page number - /// - pageSize: Page size - /// - Returns: Paginated list of payment transactions - public func listPaymentTransactions( - subscriberId: Int64? = nil, - status: PaymentStatus? = nil, - page: Int = 1, - pageSize: Int = 50 - ) async throws -> PaymentTransactionList { - var queryParams: [String: String] = [ - "page": "\(page)", - "page_size": "\(pageSize)" - ] - - if let subscriberId = subscriberId { - queryParams["subscriber_id"] = "\(subscriberId)" - } - - if let status = status { - queryParams["status"] = status.rawValue - } - - return try await httpClient.get(path: "/v1/payments/transactions", queryParams: queryParams) - } - - // MARK: - Rating Plans - - /// List all available rating plans - /// - Returns: List of rating plans - public func listRatingPlans() async throws -> [RatingPlan] { - return try await httpClient.get(path: "/v1/rating-plans") - } - - /// Get rating plan by ID - /// - Parameter planId: Plan ID - /// - Returns: Rating plan - public func getRatingPlan(planId: String) async throws -> RatingPlan { - return try await httpClient.get(path: "/v1/rating-plans/\(planId)") - } - - // MARK: - System Management - - /// Get system statistics - /// - Returns: System statistics - public func getSystemStats() async throws -> SystemStats { - return try await httpClient.get(path: "/v1/system/stats") - } - - /// Get system health status - /// - Returns: Health status - public func getHealthStatus() async throws -> HealthStatus { - return try await httpClient.get(path: "/v1/health") - } - - // MARK: - WebSocket Support - - /// Connect to WebSocket for real-time updates - /// - Parameter messageHandler: Closure to handle WebSocket messages - /// - Returns: Async stream of WebSocket messages - public func connectWebSocket( - messageHandler: @escaping (WebSocketMessage) async -> Void - ) -> AsyncThrowingStream { - return try await webSocketClient.connect(messageHandler: messageHandler) - } - - // MARK: - GraphQL Support - - /// Execute a GraphQL query - /// - Parameters: - /// - query: GraphQL query string - /// - variables: Optional query variables - /// - Returns: GraphQL response - public func executeGraphQLQuery( - query: String, - variables: [String: Any]? = nil - ) async throws -> GraphQLResponse { - let request = GraphQLRequest(query: query, variables: variables ?? [:]) - return try await httpClient.post(path: "/graphql", body: request) + /// Close the SDK and cleanup resources + public func close() { + httpClient.close() } } @@ -280,20 +67,28 @@ public struct TelecomConfig { public var apiURL: String /// API key for authentication public var apiKey: String? + /// JWT secret for token generation + public var jwtSecret: String? /// Request timeout in seconds public var timeout: TimeInterval /// Maximum number of retry attempts public var maxRetries: Int + /// Delay between retries in seconds + public var retryDelay: TimeInterval public init( apiURL: String = "http://localhost:8000", apiKey: String? = nil, + jwtSecret: String? = nil, timeout: TimeInterval = 30.0, - maxRetries: Int = 3 + maxRetries: Int = 3, + retryDelay: TimeInterval = 1.0 ) { self.apiURL = apiURL self.apiKey = apiKey + self.jwtSecret = jwtSecret self.timeout = timeout self.maxRetries = maxRetries + self.retryDelay = retryDelay } } diff --git a/sdk/swift/Sources/TelecomSDK/UsageAPI.swift b/sdk/swift/Sources/TelecomSDK/UsageAPI.swift new file mode 100644 index 0000000..01ed422 --- /dev/null +++ b/sdk/swift/Sources/TelecomSDK/UsageAPI.swift @@ -0,0 +1,58 @@ +import Foundation + +/// API for usage management +public class UsageAPI { + private let client: HTTPClient + + public init(client: HTTPClient) { + self.client = client + } + + /// Get usage statistics for a subscriber + public func getStats(subscriberID: Int64, startDate: Date, endDate: Date) async throws -> UsageStats { + let formatter = ISO8601DateFormatter() + let params: [String: String] = [ + "start_date": formatter.string(from: startDate), + "end_date": formatter.string(from: endDate) + ] + + return try await client.get(path: "/v1/subscribers/\(subscriberID)/usage", params: params) + } + + /// List usage events + public func listEvents( + subscriberID: Int64? = nil, + usageType: UsageType? = nil, + startDate: Date? = nil, + endDate: Date? = nil, + page: Int = 1, + pageSize: Int = 50 + ) async throws -> UsageEventList { + var params: [String: String] = [ + "page": "\(page)", + "page_size": "\(pageSize)" + ] + + if let subscriberID = subscriberID { + params["subscriber_id"] = "\(subscriberID)" + } + if let usageType = usageType { + params["usage_type"] = "\(usageType)" + } + if let startDate = startDate { + let formatter = ISO8601DateFormatter() + params["start_date"] = formatter.string(from: startDate) + } + if let endDate = endDate { + let formatter = ISO8601DateFormatter() + params["end_date"] = formatter.string(from: endDate) + } + + return try await client.get(path: "/v1/usage/events", params: params) + } + + /// Get real-time usage for a subscriber + public func getRealtime(subscriberID: Int64) async throws -> RealTimeUsage { + return try await client.get(path: "/v1/subscribers/\(subscriberID)/realtime") + } +}