mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
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
This commit is contained in:
@@ -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: "[email protected]",
|
||||
planId: 1
|
||||
)
|
||||
|
||||
// Update subscriber
|
||||
let updated = try await sdk.subscribers.update(
|
||||
id: 1,
|
||||
firstName: "Jane",
|
||||
email: "[email protected]"
|
||||
)
|
||||
|
||||
// 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.
|
||||
@@ -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<SHA256>.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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<T: Decodable>(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<T: Decodable>(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<T: Decodable>(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<T: Decodable>(
|
||||
_ 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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<WebSocketMessage, Error> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user