From d846c304a40e18833d73a35f568d65eeec654025 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sat, 25 Apr 2026 04:56:33 +0300 Subject: [PATCH] docs: Add Go SDK README with comprehensive API documentation and implement modular architecture with authentication provider - 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, PaymentsAPI, RatingPlansAPI --- sdk/go/README.md | 72 +++++++-- sdk/go/auth.go | 151 +++++++++++++++++++ sdk/go/client.go | 134 +++++++++++++++++ sdk/go/graphql_api.go | 29 ++++ sdk/go/payments_api.go | 49 +++++++ sdk/go/rating_plans_api.go | 30 ++++ sdk/go/subscribers_api.go | 71 +++++++++ sdk/go/system_api.go | 29 ++++ sdk/go/telecom.go | 290 ++++++++----------------------------- sdk/go/usage_api.go | 61 ++++++++ sdk/python/README.md | 10 ++ 11 files changed, 682 insertions(+), 244 deletions(-) create mode 100644 sdk/go/auth.go create mode 100644 sdk/go/client.go create mode 100644 sdk/go/graphql_api.go create mode 100644 sdk/go/payments_api.go create mode 100644 sdk/go/rating_plans_api.go create mode 100644 sdk/go/subscribers_api.go create mode 100644 sdk/go/system_api.go create mode 100644 sdk/go/usage_api.go diff --git a/sdk/go/README.md b/sdk/go/README.md index d9d3228..69063e6 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -1,9 +1,10 @@ # Telecom Platform Go SDK -Go SDK for the Telecom Platform with both HTTP and gRPC support. +Go SDK for the Telecom Platform with modular architecture for HTTP and gRPC support. ## Features +- **Modular Architecture**: Separate modules for authentication, HTTP client, and API services - **Dual Protocol Support**: Both HTTP REST and gRPC APIs - **Type Safety**: Strong typing with Go structs - **Context Support**: Full context.Context support @@ -11,6 +12,20 @@ Go SDK for the Telecom Platform with both HTTP and gRPC support. - **Retry Logic**: Built-in retry mechanism - **Async Operations**: Non-blocking operations +## Architecture + +The SDK is organized into modular components: + +- `auth.go` - Authentication provider handling API keys and JWT tokens +- `client.go` - HTTP client with retry logic and authentication +- `subscribers_api.go` - Subscriber management API +- `usage_api.go` - Usage tracking API +- `payments_api.go` - Payment processing API +- `rating_plans_api.go` - Rating plan management API +- `system_api.go` - System monitoring API +- `graphql_api.go` - GraphQL query execution +- `telecom.go` - Main client integrating all modules + ## Installation ```bash @@ -45,7 +60,7 @@ func main() { ctx := context.Background() // Get subscriber - subscriber, err := client.GetSubscriber(ctx, 1) + subscriber, err := client.Subscribers.Get(ctx, 1) if err != nil { panic(err) } @@ -53,7 +68,7 @@ func main() { fmt.Printf("Subscriber: %s %s\n", subscriber.FirstName, subscriber.LastName) // List subscribers - list, err := client.ListSubscribers(ctx, 1, 50) + list, err := client.Subscribers.List(ctx, 1, 50) if err != nil { panic(err) } @@ -119,10 +134,10 @@ config := &telecom.Config{ ```go // Get subscriber -subscriber, err := client.GetSubscriber(ctx, 1) +subscriber, err := client.Subscribers.Get(ctx, 1) // List subscribers -list, err := client.ListSubscribers(ctx, 1, 50) +list, err := client.Subscribers.List(ctx, 1, 50, "") // Create subscriber req := &telecom.CreateSubscriberRequest{ @@ -133,51 +148,82 @@ req := &telecom.CreateSubscriberRequest{ Email: "john.doe@example.com", PlanID: 1, } -subscriber, err := client.CreateSubscriber(ctx, req) +subscriber, err := client.Subscribers.Create(ctx, req) // Update subscriber updateReq := &telecom.UpdateSubscriberRequest{ FirstName: stringPtr("Jane"), } -subscriber, err := client.UpdateSubscriber(ctx, 1, updateReq) +subscriber, err := client.Subscribers.Update(ctx, 1, updateReq) // Delete subscriber -err := client.DeleteSubscriber(ctx, 1) +err := client.Subscribers.Delete(ctx, 1) + +// Suspend subscriber +subscriber, err := client.Subscribers.Suspend(ctx, 1) + +// Activate subscriber +subscriber, err := client.Subscribers.Activate(ctx, 1) ``` ### Usage Management ```go // Get usage stats -stats, err := client.GetUsageStats(ctx, 1, time.Now().AddDate(0, -1, 0), time.Now()) +stats, err := client.Usage.GetStats(ctx, 1, time.Now().AddDate(0, -1, 0), time.Now()) fmt.Printf("Data usage: %d bytes\n", stats.DataUp + stats.DataDown) + +// List usage events +events, err := client.Usage.ListEvents(ctx, 1, "", 1, 50) + +// Get real-time usage +realtime, err := client.Usage.GetRealTime(ctx, 1) ``` ### Payment Management ```go -// Create payment +// Create payment transaction req := &telecom.CreatePaymentRequest{ SubscriberID: "1", Amount: 25.00, Currency: "USD", Gateway: "stripe", } -transaction, err := client.CreatePaymentTransaction(ctx, req) +transaction, err := client.Payments.CreateTransaction(ctx, req) + +// Get transaction +transaction, err := client.Payments.GetTransaction(ctx, "txn-123") + +// List transactions +transactions, err := client.Payments.ListTransactions(ctx, 1, "", 1, 50) +``` + +### Rating Plans + +```go +// List rating plans +plans, err := client.RatingPlans.List(ctx) + +// Get specific plan +plan, err := client.RatingPlans.Get(ctx, "plan-123") ``` ### System Management ```go // Get system stats -stats, err := client.GetSystemStats(ctx) +stats, err := client.System.GetStats(ctx) fmt.Printf("Active sessions: %d\n", stats.ActiveSessions) + +// Get health status +health, err := client.System.GetHealth(ctx) ``` ## Error Handling ```go -subscriber, err := client.GetSubscriber(ctx, 1) +subscriber, err := client.Subscribers.Get(ctx, 1) if err != nil { switch { case strings.Contains(err.Error(), "HTTP error: 401"): diff --git a/sdk/go/auth.go b/sdk/go/auth.go new file mode 100644 index 0000000..3323715 --- /dev/null +++ b/sdk/go/auth.go @@ -0,0 +1,151 @@ +package telecom + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "strings" + "time" +) + +// AuthProvider handles authentication for the Telecom SDK +type AuthProvider struct { + apiKey string + jwtSecret string + tokenCache string + tokenExpiry time.Time +} + +// NewAuthProvider creates a new authentication provider +func NewAuthProvider(apiKey, jwtSecret string) *AuthProvider { + return &AuthProvider{ + apiKey: apiKey, + jwtSecret: jwtSecret, + } +} + +// GetHeaders returns authentication headers for API requests +func (a *AuthProvider) GetHeaders() map[string]string { + headers := map[string]string{ + "Content-Type": "application/json", + "User-Agent": "Telecom-Go-SDK/1.0.0", + } + + if a.apiKey != "" { + headers["X-API-Key"] = a.apiKey + } + + if a.tokenCache != "" && a.isTokenValid() { + headers["Authorization"] = "Bearer " + a.tokenCache + } + + return headers +} + +// GenerateJWTToken generates a JWT token for authentication +func (a *AuthProvider) GenerateJWTToken(userID string, expiryHours int, additionalClaims map[string]interface{}) (string, error) { + if a.jwtSecret == "" { + return "", fmt.Errorf("JWT secret not configured") + } + + now := time.Now().Unix() + claims := map[string]interface{}{ + "sub": userID, + "exp": now + int64(expiryHours*3600), + "iat": now, + } + + for k, v := range additionalClaims { + claims[k] = v + } + + header := map[string]interface{}{ + "alg": "HS256", + "typ": "JWT", + } + + encodedHeader := a.base64URLEncode(header) + encodedPayload := a.base64URLEncode(claims) + signature := a.sign(encodedHeader + "." + encodedPayload) + + token := encodedHeader + "." + encodedPayload + "." + signature + a.tokenCache = token + a.tokenExpiry = time.Unix(claims["exp"].(int64), 0) + + return token, nil +} + +// ValidateJWTToken validates a JWT token +func (a *AuthProvider) ValidateJWTToken(token string) (map[string]interface{}, error) { + if a.jwtSecret == "" { + return nil, fmt.Errorf("JWT secret not configured") + } + + parts := strings.Split(token, ".") + if len(parts) != 3 { + return nil, fmt.Errorf("invalid token format") + } + + encodedHeader, encodedPayload, signature := parts[0], parts[1], parts[2] + expectedSignature := a.sign(encodedHeader + "." + encodedPayload) + + if signature != expectedSignature { + return nil, fmt.Errorf("invalid token signature") + } + + payloadBytes, err := a.base64URLDecode(encodedPayload) + if err != nil { + return nil, fmt.Errorf("failed to decode payload: %w", err) + } + + var payload map[string]interface{} + if err := json.Unmarshal(payloadBytes, &payload); err != nil { + return nil, fmt.Errorf("failed to unmarshal payload: %w", err) + } + + if exp, ok := payload["exp"].(float64); ok { + if float64(time.Now().Unix()) > exp { + return nil, fmt.Errorf("token has expired") + } + } + + return payload, nil +} + +// ClearTokenCache clears the cached JWT token +func (a *AuthProvider) ClearTokenCache() { + a.tokenCache = "" + a.tokenExpiry = time.Time{} +} + +// isTokenValid checks if the cached token is still valid +func (a *AuthProvider) isTokenValid() bool { + if a.tokenCache == "" || a.tokenExpiry.IsZero() { + return false + } + return time.Now().Before(a.tokenExpiry) +} + +// base64URLEncode encodes data to base64 URL-safe format +func (a *AuthProvider) base64URLEncode(data interface{}) string { + jsonBytes, _ := json.Marshal(data) + return strings.TrimRight(base64.URLEncoding.EncodeToString(jsonBytes), "=") +} + +// base64URLDecode decodes base64 URL-safe format +func (a *AuthProvider) base64URLDecode(data string) ([]byte, error) { + // Add padding if needed + for len(data)%4 != 0 { + data += "=" + } + return base64.URLEncoding.DecodeString(data) +} + +// sign creates a HMAC-SHA256 signature +func (a *AuthProvider) sign(data string) string { + h := hmac.New(sha256.New, []byte(a.jwtSecret)) + h.Write([]byte(data)) + return a.base64URLEncode(h.Sum(nil)) +} diff --git a/sdk/go/client.go b/sdk/go/client.go new file mode 100644 index 0000000..b7f9b48 --- /dev/null +++ b/sdk/go/client.go @@ -0,0 +1,134 @@ +package telecom + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// HTTPClient handles HTTP requests with authentication +type HTTPClient struct { + client *http.Client + authProvider *AuthProvider + baseURL string + timeout time.Duration + maxRetries int + retryDelay time.Duration + enableLogging bool +} + +// NewHTTPClient creates a new HTTP client +func NewHTTPClient(baseURL string, authProvider *AuthProvider, timeout time.Duration, maxRetries int, retryDelay time.Duration, enableLogging bool) *HTTPClient { + return &HTTPClient{ + client: &http.Client{ + Timeout: timeout, + }, + authProvider: authProvider, + baseURL: baseURL, + timeout: timeout, + maxRetries: maxRetries, + retryDelay: retryDelay, + enableLogging: enableLogging, + } +} + +// Get makes an HTTP GET request +func (h *HTTPClient) Get(ctx context.Context, path string, result interface{}, params ...map[string]string) error { + return h.request(ctx, "GET", path, nil, result, params...) +} + +// Post makes an HTTP POST request +func (h *HTTPClient) Post(ctx context.Context, path string, body, result interface{}) error { + return h.request(ctx, "POST", path, body, result) +} + +// Put makes an HTTP PUT request +func (h *HTTPClient) Put(ctx context.Context, path string, body, result interface{}) error { + return h.request(ctx, "PUT", path, body, result) +} + +// Delete makes an HTTP DELETE request +func (h *HTTPClient) Delete(ctx context.Context, path string) error { + return h.request(ctx, "DELETE", path, nil, nil) +} + +// request makes an HTTP request with retry logic +func (h *HTTPClient) request(ctx context.Context, method, path string, body, result interface{}, params ...map[string]string) error { + var lastErr error + + for attempt := 0; attempt <= h.maxRetries; attempt++ { + var bodyBytes []byte + var err error + + if body != nil { + bodyBytes, err = json.Marshal(body) + if err != nil { + return fmt.Errorf("failed to marshal body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, h.baseURL+path, bytes.NewReader(bodyBytes)) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + // Set headers + headers := h.authProvider.GetHeaders() + for k, v := range headers { + req.Header.Set(k, v) + } + + // Add query parameters + if len(params) > 0 { + q := req.URL.Query() + for k, v := range params[0] { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := h.client.Do(req) + if err != nil { + lastErr = err + if attempt == h.maxRetries { + return fmt.Errorf("request failed after %d retries: %w", h.maxRetries, err) + } + time.Sleep(h.retryDelay * time.Duration(1<= 400 && resp.StatusCode < 500 { + var errorResp map[string]interface{} + json.NewDecoder(resp.Body).Decode(&errorResp) + return fmt.Errorf("API error: %v", errorResp) + } + if resp.StatusCode >= 500 { + return fmt.Errorf("server error: %d", resp.StatusCode) + } + + if result != nil { + if err := json.NewDecoder(resp.Body).Decode(result); err != nil { + return fmt.Errorf("failed to decode response: %w", err) + } + } + + return nil + } + + return lastErr +} + +// Close closes the HTTP client +func (h *HTTPClient) Close() error { + return nil +} diff --git a/sdk/go/graphql_api.go b/sdk/go/graphql_api.go new file mode 100644 index 0000000..cc28387 --- /dev/null +++ b/sdk/go/graphql_api.go @@ -0,0 +1,29 @@ +package telecom + +import ( + "context" +) + +// GraphQLAPI handles GraphQL API calls +type GraphQLAPI struct { + client *HTTPClient +} + +// NewGraphQLAPI creates a new GraphQLAPI +func NewGraphQLAPI(client *HTTPClient) *GraphQLAPI { + return &GraphQLAPI{client: client} +} + +// Execute executes a GraphQL query +func (g *GraphQLAPI) Execute(ctx context.Context, query string, variables map[string]interface{}) (map[string]interface{}, error) { + data := map[string]interface{}{ + "query": query, + } + if variables != nil { + data["variables"] = variables + } + + var result map[string]interface{} + err := g.client.Post(ctx, "/graphql", data, &result) + return result, err +} diff --git a/sdk/go/payments_api.go b/sdk/go/payments_api.go new file mode 100644 index 0000000..b0efcce --- /dev/null +++ b/sdk/go/payments_api.go @@ -0,0 +1,49 @@ +package telecom + +import ( + "context" + "fmt" +) + +// PaymentAPI handles payment-related API calls +type PaymentAPI struct { + client *HTTPClient +} + +// NewPaymentAPI creates a new PaymentAPI +func NewPaymentAPI(client *HTTPClient) *PaymentAPI { + return &PaymentAPI{client: client} +} + +// CreateTransaction creates a new payment transaction +func (p *PaymentAPI) CreateTransaction(ctx context.Context, req *CreatePaymentRequest) (*PaymentTransaction, error) { + var transaction PaymentTransaction + err := p.client.Post(ctx, "/v1/payments/transactions", req, &transaction) + return &transaction, err +} + +// GetTransaction retrieves a payment transaction by ID +func (p *PaymentAPI) GetTransaction(ctx context.Context, transactionID string) (*PaymentTransaction, error) { + var transaction PaymentTransaction + err := p.client.Get(ctx, fmt.Sprintf("/v1/payments/transactions/%s", transactionID), &transaction) + return &transaction, err +} + +// ListTransactions retrieves a list of payment transactions +func (p *PaymentAPI) ListTransactions(ctx context.Context, subscriberID int64, status string, page, pageSize int32) (*SubscriberList, error) { + params := map[string]string{ + "page": fmt.Sprintf("%d", page), + "page_size": fmt.Sprintf("%d", pageSize), + } + + if subscriberID > 0 { + params["subscriber_id"] = fmt.Sprintf("%d", subscriberID) + } + if status != "" { + params["status"] = status + } + + var list SubscriberList + err := p.client.Get(ctx, "/v1/payments/transactions", &list, params) + return &list, err +} diff --git a/sdk/go/rating_plans_api.go b/sdk/go/rating_plans_api.go new file mode 100644 index 0000000..a5fb104 --- /dev/null +++ b/sdk/go/rating_plans_api.go @@ -0,0 +1,30 @@ +package telecom + +import ( + "context" + "fmt" +) + +// RatingPlanAPI handles rating plan-related API calls +type RatingPlanAPI struct { + client *HTTPClient +} + +// NewRatingPlanAPI creates a new RatingPlanAPI +func NewRatingPlanAPI(client *HTTPClient) *RatingPlanAPI { + return &RatingPlanAPI{client: client} +} + +// List retrieves all available rating plans +func (r *RatingPlanAPI) List(ctx context.Context) ([]RatingPlan, error) { + var plans []RatingPlan + err := r.client.Get(ctx, "/v1/rating-plans", &plans) + return plans, err +} + +// Get retrieves a rating plan by ID +func (r *RatingPlanAPI) Get(ctx context.Context, planID string) (*RatingPlan, error) { + var plan RatingPlan + err := r.client.Get(ctx, fmt.Sprintf("/v1/rating-plans/%s", planID), &plan) + return &plan, err +} diff --git a/sdk/go/subscribers_api.go b/sdk/go/subscribers_api.go new file mode 100644 index 0000000..5d91b5a --- /dev/null +++ b/sdk/go/subscribers_api.go @@ -0,0 +1,71 @@ +package telecom + +import ( + "context" + "fmt" +) + +// SubscriberAPI handles subscriber-related API calls +type SubscriberAPI struct { + client *HTTPClient +} + +// NewSubscriberAPI creates a new SubscriberAPI +func NewSubscriberAPI(client *HTTPClient) *SubscriberAPI { + return &SubscriberAPI{client: client} +} + +// Get retrieves a subscriber by ID +func (s *SubscriberAPI) Get(ctx context.Context, id int64) (*Subscriber, error) { + var subscriber Subscriber + err := s.client.Get(ctx, fmt.Sprintf("/v1/subscribers/%d", id), &subscriber) + return &subscriber, err +} + +// List retrieves a list of subscribers +func (s *SubscriberAPI) List(ctx context.Context, page, pageSize int32, status string) (*SubscriberList, error) { + params := map[string]string{ + "page": fmt.Sprintf("%d", page), + "page_size": fmt.Sprintf("%d", pageSize), + } + if status != "" { + params["status"] = status + } + + var list SubscriberList + err := s.client.Get(ctx, "/v1/subscribers", &list, params) + return &list, err +} + +// Create creates a new subscriber +func (s *SubscriberAPI) Create(ctx context.Context, req *CreateSubscriberRequest) (*Subscriber, error) { + var subscriber Subscriber + err := s.client.Post(ctx, "/v1/subscribers", req, &subscriber) + return &subscriber, err +} + +// Update updates an existing subscriber +func (s *SubscriberAPI) Update(ctx context.Context, id int64, req *UpdateSubscriberRequest) (*Subscriber, error) { + var subscriber Subscriber + err := s.client.Put(ctx, fmt.Sprintf("/v1/subscribers/%d", id), req, &subscriber) + return &subscriber, err +} + +// Delete deletes a subscriber +func (s *SubscriberAPI) Delete(ctx context.Context, id int64) error { + return s.client.Delete(ctx, fmt.Sprintf("/v1/subscribers/%d", id)) +} + +// Suspend suspends a subscriber +func (s *SubscriberAPI) Suspend(ctx context.Context, id int64) (*Subscriber, error) { + var subscriber Subscriber + err := s.client.Post(ctx, fmt.Sprintf("/v1/subscribers/%d/suspend", id), nil, &subscriber) + return &subscriber, err +} + +// Activate activates a suspended subscriber +func (s *SubscriberAPI) Activate(ctx context.Context, id int64) (*Subscriber, error) { + var subscriber Subscriber + err := s.client.Post(ctx, fmt.Sprintf("/v1/subscribers/%d/activate", id), nil, &subscriber) + return &subscriber, err +} diff --git a/sdk/go/system_api.go b/sdk/go/system_api.go new file mode 100644 index 0000000..30df002 --- /dev/null +++ b/sdk/go/system_api.go @@ -0,0 +1,29 @@ +package telecom + +import ( + "context" +) + +// SystemAPI handles system-related API calls +type SystemAPI struct { + client *HTTPClient +} + +// NewSystemAPI creates a new SystemAPI +func NewSystemAPI(client *HTTPClient) *SystemAPI { + return &SystemAPI{client: client} +} + +// GetStats retrieves system statistics +func (s *SystemAPI) GetStats(ctx context.Context) (*SystemStats, error) { + var stats SystemStats + err := s.client.Get(ctx, "/v1/system/stats", &stats) + return &stats, err +} + +// GetHealth retrieves system health status +func (s *SystemAPI) GetHealth(ctx context.Context) (*HealthStatus, error) { + var health HealthStatus + err := s.client.Get(ctx, "/v1/health", &health) + return &health, err +} diff --git a/sdk/go/telecom.go b/sdk/go/telecom.go index 4ba9a5a..551ef5b 100644 --- a/sdk/go/telecom.go +++ b/sdk/go/telecom.go @@ -1,44 +1,45 @@ package telecom import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" "time" ) // Client represents the Telecom Platform SDK client type Client struct { - config *Config - httpClient *HTTPClient + config *Config + authProvider *AuthProvider + httpClient *HTTPClient + + // API modules + Subscribers *SubscriberAPI + Usage *UsageAPI + Payments *PaymentAPI + RatingPlans *RatingPlanAPI + System *SystemAPI + GraphQL *GraphQLAPI } // Config holds the SDK configuration type Config struct { - APIURL string - APIKey string - Timeout time.Duration - Retries int - EnableHTTP bool -} - -// HTTPClient handles HTTP requests -type HTTPClient struct { - client *http.Client - apiKey string - baseURL string + APIURL string + APIKey string + JWTSecret string + Timeout time.Duration + MaxRetries int + RetryDelay time.Duration + EnableHTTP bool + EnableLogging bool } // DefaultConfig returns a default configuration func DefaultConfig() *Config { return &Config{ - APIURL: "http://localhost:8000", - APIKey: "", - Timeout: 30 * time.Second, - Retries: 3, - EnableHTTP: true, + APIURL: "http://localhost:8000", + Timeout: 30 * time.Second, + MaxRetries: 3, + RetryDelay: 1 * time.Second, + EnableHTTP: true, + EnableLogging: false, } } @@ -48,226 +49,53 @@ func NewClient(config *Config) (*Client, error) { config = DefaultConfig() } - client := &Client{ - config: config, + authProvider := NewAuthProvider(config.APIKey, config.JWTSecret) + + var httpClient *HTTPClient + if config.EnableHTTP { + httpClient = NewHTTPClient( + config.APIURL, + authProvider, + config.Timeout, + config.MaxRetries, + config.RetryDelay, + config.EnableLogging, + ) } - // Initialize HTTP client - if config.EnableHTTP { - client.httpClient = &HTTPClient{ - client: &http.Client{ - Timeout: config.Timeout, - }, - apiKey: config.APIKey, - baseURL: config.APIURL, - } + client := &Client{ + config: config, + authProvider: authProvider, + httpClient: httpClient, } + // Initialize API modules + client.Subscribers = NewSubscriberAPI(httpClient) + client.Usage = NewUsageAPI(httpClient) + client.Payments = NewPaymentAPI(httpClient) + client.RatingPlans = NewRatingPlanAPI(httpClient) + client.System = NewSystemAPI(httpClient) + client.GraphQL = NewGraphQLAPI(httpClient) + return client, nil } // Close closes the client connections func (c *Client) Close() error { + if c.httpClient != nil { + return c.httpClient.Close() + } return nil } -// HTTP Methods +// Authentication methods -// GetSubscriber retrieves a subscriber by ID -func (c *Client) GetSubscriber(ctx context.Context, id int64) (*Subscriber, error) { - if !c.config.EnableHTTP { - return nil, fmt.Errorf("HTTP client not enabled") - } - - var subscriber Subscriber - err := c.httpClient.Get(ctx, fmt.Sprintf("/v1/subscribers/%d", id), &subscriber) - return &subscriber, err +// GenerateJWTToken generates a JWT token for authentication +func (c *Client) GenerateJWTToken(userID string, expiryHours int, additionalClaims map[string]interface{}) (string, error) { + return c.authProvider.GenerateJWTToken(userID, expiryHours, additionalClaims) } -// ListSubscribers retrieves a list of subscribers -func (c *Client) ListSubscribers(ctx context.Context, page, pageSize int32) (*SubscriberList, error) { - if !c.config.EnableHTTP { - return nil, fmt.Errorf("HTTP client not enabled") - } - - params := map[string]string{ - "page": fmt.Sprintf("%d", page), - "page_size": fmt.Sprintf("%d", pageSize), - } - - var list SubscriberList - err := c.httpClient.Get(ctx, "/v1/subscribers", &list, params) - return &list, err -} - -// CreateSubscriber creates a new subscriber -func (c *Client) CreateSubscriber(ctx context.Context, req *CreateSubscriberRequest) (*Subscriber, error) { - if !c.config.EnableHTTP { - return nil, fmt.Errorf("HTTP client not enabled") - } - - var subscriber Subscriber - err := c.httpClient.Post(ctx, "/v1/subscribers", req, &subscriber) - return &subscriber, err -} - -// UpdateSubscriber updates an existing subscriber -func (c *Client) UpdateSubscriber(ctx context.Context, id int64, req *UpdateSubscriberRequest) (*Subscriber, error) { - if !c.config.EnableHTTP { - return nil, fmt.Errorf("HTTP client not enabled") - } - - var subscriber Subscriber - err := c.httpClient.Put(ctx, fmt.Sprintf("/v1/subscribers/%d", id), req, &subscriber) - return &subscriber, err -} - -// DeleteSubscriber deletes a subscriber -func (c *Client) DeleteSubscriber(ctx context.Context, id int64) error { - if !c.config.EnableHTTP { - return fmt.Errorf("HTTP client not enabled") - } - - return c.httpClient.Delete(ctx, fmt.Sprintf("/v1/subscribers/%d", id)) -} - -// GetUsageStats retrieves usage statistics for a subscriber -func (c *Client) GetUsageStats(ctx context.Context, subscriberID int64, startDate, endDate time.Time) (*UsageStats, error) { - if !c.config.EnableHTTP { - return nil, fmt.Errorf("HTTP client not enabled") - } - - params := map[string]string{ - "start_date": startDate.Format(time.RFC3339), - "end_date": endDate.Format(time.RFC3339), - } - - var stats UsageStats - err := c.httpClient.Get(ctx, fmt.Sprintf("/v1/subscribers/%d/usage", subscriberID), &stats, params) - return &stats, err -} - -// CreatePaymentTransaction creates a new payment transaction -func (c *Client) CreatePaymentTransaction(ctx context.Context, req *CreatePaymentRequest) (*PaymentTransaction, error) { - if !c.config.EnableHTTP { - return nil, fmt.Errorf("HTTP client not enabled") - } - - var transaction PaymentTransaction - err := c.httpClient.Post(ctx, "/v1/payments/transactions", req, &transaction) - return &transaction, err -} - -// GetSystemStats retrieves system statistics -func (c *Client) GetSystemStats(ctx context.Context) (*SystemStats, error) { - if !c.config.EnableHTTP { - return nil, fmt.Errorf("HTTP client not enabled") - } - - var stats SystemStats - err := c.httpClient.Get(ctx, "/v1/system/stats", &stats) - return &stats, err -} - -// HTTPClient implementation - -// Get makes an HTTP GET request -func (h *HTTPClient) Get(ctx context.Context, path string, result interface{}, params ...map[string]string) error { - req, err := http.NewRequestWithContext(ctx, "GET", h.baseURL+path, nil) - if err != nil { - return err - } - - if h.apiKey != "" { - req.Header.Set("Authorization", "Bearer "+h.apiKey) - } - - if len(params) > 0 { - q := req.URL.Query() - for k, v := range params[0] { - q.Add(k, v) - } - req.URL.RawQuery = q.Encode() - } - - resp, err := h.client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("HTTP error: %d", resp.StatusCode) - } - - return json.NewDecoder(resp.Body).Decode(result) -} - -// Post makes an HTTP POST request -func (h *HTTPClient) Post(ctx context.Context, path string, body, result interface{}) error { - return h.doRequest(ctx, "POST", path, body, result) -} - -// Put makes an HTTP PUT request -func (h *HTTPClient) Put(ctx context.Context, path string, body, result interface{}) error { - return h.doRequest(ctx, "PUT", path, body, result) -} - -// Delete makes an HTTP DELETE request -func (h *HTTPClient) Delete(ctx context.Context, path string) error { - req, err := http.NewRequestWithContext(ctx, "DELETE", h.baseURL+path, nil) - if err != nil { - return err - } - - if h.apiKey != "" { - req.Header.Set("Authorization", "Bearer "+h.apiKey) - } - - resp, err := h.client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { - return fmt.Errorf("HTTP error: %d", resp.StatusCode) - } - - return nil -} - -// doRequest makes an HTTP request with a body -func (h *HTTPClient) doRequest(ctx context.Context, method, path string, body, result interface{}) error { - var bodyBytes []byte - var err error - - if body != nil { - bodyBytes, err = json.Marshal(body) - if err != nil { - return err - } - } - - req, err := http.NewRequestWithContext(ctx, method, h.baseURL+path, bytes.NewReader(bodyBytes)) - if err != nil { - return err - } - - if h.apiKey != "" { - req.Header.Set("Authorization", "Bearer "+h.apiKey) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := h.client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("HTTP error: %d", resp.StatusCode) - } - - return json.NewDecoder(resp.Body).Decode(result) +// ValidateJWTToken validates a JWT token +func (c *Client) ValidateJWTToken(token string) (map[string]interface{}, error) { + return c.authProvider.ValidateJWTToken(token) } diff --git a/sdk/go/usage_api.go b/sdk/go/usage_api.go new file mode 100644 index 0000000..3ed9171 --- /dev/null +++ b/sdk/go/usage_api.go @@ -0,0 +1,61 @@ +package telecom + +import ( + "context" + "fmt" + "time" +) + +// UsageAPI handles usage-related API calls +type UsageAPI struct { + client *HTTPClient +} + +// NewUsageAPI creates a new UsageAPI +func NewUsageAPI(client *HTTPClient) *UsageAPI { + return &UsageAPI{client: client} +} + +// GetStats retrieves usage statistics for a subscriber +func (u *UsageAPI) GetStats(ctx context.Context, subscriberID int64, startDate, endDate time.Time) (*UsageStats, error) { + params := map[string]string{ + "start_date": startDate.Format(time.RFC3339), + "end_date": endDate.Format(time.RFC3339), + } + + var stats UsageStats + err := u.client.Get(ctx, fmt.Sprintf("/v1/subscribers/%d/usage", subscriberID), &stats, params) + return &stats, err +} + +// ListEvents retrieves a list of usage events +func (u *UsageAPI) ListEvents(ctx context.Context, subscriberID int64, usageType string, startDate, endDate time.Time, page, pageSize int32) (*UsageEventList, error) { + params := map[string]string{ + "page": fmt.Sprintf("%d", page), + "page_size": fmt.Sprintf("%d", pageSize), + } + + if subscriberID > 0 { + params["subscriber_id"] = fmt.Sprintf("%d", subscriberID) + } + if usageType != "" { + params["usage_type"] = usageType + } + if !startDate.IsZero() { + params["start_date"] = startDate.Format(time.RFC3339) + } + if !endDate.IsZero() { + params["end_date"] = endDate.Format(time.RFC3339) + } + + var list UsageEventList + err := u.client.Get(ctx, "/v1/usage/events", &list, params) + return &list, err +} + +// GetRealtime retrieves real-time usage for a subscriber +func (u *UsageAPI) GetRealtime(ctx context.Context, subscriberID int64) (*RealTimeUsage, error) { + var usage RealTimeUsage + err := u.client.Get(ctx, fmt.Sprintf("/v1/subscribers/%d/realtime", subscriberID), &usage) + return &usage, err +} diff --git a/sdk/python/README.md b/sdk/python/README.md index cb49889..dd7415b 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -4,6 +4,7 @@ Async Python SDK for the Telecom Platform with full type hints and modern async/ ## Features +- **Modular Architecture**: Separate modules for authentication, API clients, and WebSocket - **Async/Await Support**: Full async/await support with aiohttp - **Type Safety**: Complete type hints with Pydantic models - **WebSocket Support**: Real-time updates via WebSocket @@ -32,6 +33,15 @@ pip install -r requirements.txt pip install -e . ``` +## Architecture + +The SDK is organized into modular components: + +- `auth.py` - Authentication provider handling API keys and JWT tokens +- `api.py` - Main API client with HTTP request handling and retry logic +- `websocket.py` - WebSocket client for real-time updates +- `types.py` - Type definitions and Pydantic models + ## Quick Start ```python