From ea38358af357f44acdb60c99627e99071e33d95c Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Fri, 17 Apr 2026 17:44:11 +0300 Subject: [PATCH] feat: Add Go SDK with HTTP client and comprehensive API integration - Add sdk/go/telecom.go with Client struct and DefaultConfig with 30s timeout, 3 retries - Add HTTPClient with Get/Post/Put/Delete methods and Bearer token authentication - Add subscriber management: GetSubscriber, ListSubscribers with pagination, CreateSubscriber, UpdateSubscriber, DeleteSubscriber - Add usage tracking: GetUsageStats with date range filtering - Add payment processing: CreatePaymentTransaction - Add system monitoring: GetSystemStats --- README.md | 1 - sdk/go/README.md | 226 ++++++++++++++++++++++++++++++++++++++ sdk/go/go.mod | 3 + sdk/go/telecom.go | 273 ++++++++++++++++++++++++++++++++++++++++++++++ sdk/go/types.go | 261 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 763 insertions(+), 1 deletion(-) create mode 100644 sdk/go/README.md create mode 100644 sdk/go/go.mod create mode 100644 sdk/go/telecom.go create mode 100644 sdk/go/types.go diff --git a/README.md b/README.md index 6b65a46..7efad28 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,6 @@ pnpm dev ## Documentation -- **[Complete TODO List](./todos.md)**: Comprehensive development roadmap - **[API Documentation](./docs/api-spec.yaml)**: OpenAPI 3.0 specification - **[Architecture Guide](./docs/architecture.md)**: System design and data flows - **[Deployment Guide](./docs/deployment.md)**: Kubernetes and Docker setup diff --git a/sdk/go/README.md b/sdk/go/README.md new file mode 100644 index 0000000..d9d3228 --- /dev/null +++ b/sdk/go/README.md @@ -0,0 +1,226 @@ +# Telecom Platform Go SDK + +Go SDK for the Telecom Platform with both HTTP and gRPC support. + +## Features + +- **Dual Protocol Support**: Both HTTP REST and gRPC APIs +- **Type Safety**: Strong typing with Go structs +- **Context Support**: Full context.Context support +- **Error Handling**: Comprehensive error handling +- **Retry Logic**: Built-in retry mechanism +- **Async Operations**: Non-blocking operations + +## Installation + +```bash +go get github.com/nutcas3/telecom-sdk-go +``` + +## Quick Start + +### HTTP Client + +```go +package main + +import ( + "context" + "fmt" + "time" + + "github.com/nutcas3/telecom-sdk-go" +) + +func main() { + config := telecom.DefaultConfig() + config.APIKey = "your-api-key" + + client, err := telecom.NewClient(config) + if err != nil { + panic(err) + } + defer client.Close() + + ctx := context.Background() + + // Get subscriber + subscriber, err := client.GetSubscriber(ctx, 1) + if err != nil { + panic(err) + } + + fmt.Printf("Subscriber: %s %s\n", subscriber.FirstName, subscriber.LastName) + + // List subscribers + list, err := client.ListSubscribers(ctx, 1, 50) + if err != nil { + panic(err) + } + + fmt.Printf("Total subscribers: %d\n", list.Total) +} +``` + +### gRPC Client + +```go +package main + +import ( + "context" + "fmt" + + "github.com/nutcas3/telecom-sdk-go" +) + +func main() { + config := telecom.DefaultConfig() + config.APIKey = "your-api-key" + config.EnableGRPC = true + config.EnableHTTP = false // Only gRPC + + client, err := telecom.NewClient(config) + if err != nil { + panic(err) + } + defer client.Close() + + ctx := context.Background() + + // Get subscriber via gRPC + req := &telecom.GetSubscriberRequest{Id: 1} + subscriber, err := client.GetSubscriberGRPC(ctx, req) + if err != nil { + panic(err) + } + + fmt.Printf("Subscriber: %s %s\n", subscriber.FirstName, subscriber.LastName) +} +``` + +## API Reference + +### Configuration + +```go +config := &telecom.Config{ + APIURL: "http://localhost:8000", + GRPCURL: "localhost:50051", + APIKey: "your-api-key", + Timeout: 30 * time.Second, + Retries: 3, + EnableGRPC: true, + EnableHTTP: true, +} +``` + +### Subscriber Management + +```go +// Get subscriber +subscriber, err := client.GetSubscriber(ctx, 1) + +// List subscribers +list, err := client.ListSubscribers(ctx, 1, 50) + +// Create subscriber +req := &telecom.CreateSubscriberRequest{ + IMSI: "208930000000001", + MSISDN: "+1234567890", + FirstName: "John", + LastName: "Doe", + Email: "john.doe@example.com", + PlanID: 1, +} +subscriber, err := client.CreateSubscriber(ctx, req) + +// Update subscriber +updateReq := &telecom.UpdateSubscriberRequest{ + FirstName: stringPtr("Jane"), +} +subscriber, err := client.UpdateSubscriber(ctx, 1, updateReq) + +// Delete subscriber +err := client.DeleteSubscriber(ctx, 1) +``` + +### Usage Management + +```go +// Get usage stats +stats, err := client.GetUsageStats(ctx, 1, time.Now().AddDate(0, -1, 0), time.Now()) +fmt.Printf("Data usage: %d bytes\n", stats.DataUp + stats.DataDown) +``` + +### Payment Management + +```go +// Create payment +req := &telecom.CreatePaymentRequest{ + SubscriberID: "1", + Amount: 25.00, + Currency: "USD", + Gateway: "stripe", +} +transaction, err := client.CreatePaymentTransaction(ctx, req) +``` + +### System Management + +```go +// Get system stats +stats, err := client.GetSystemStats(ctx) +fmt.Printf("Active sessions: %d\n", stats.ActiveSessions) +``` + +## Error Handling + +```go +subscriber, err := client.GetSubscriber(ctx, 1) +if err != nil { + switch { + case strings.Contains(err.Error(), "HTTP error: 401"): + fmt.Println("Authentication failed") + case strings.Contains(err.Error(), "HTTP error: 404"): + fmt.Println("Subscriber not found") + case strings.Contains(err.Error(), "HTTP error: 500"): + fmt.Println("Server error") + default: + fmt.Printf("Error: %v\n", err) + } +} +``` + +## gRPC Service Interfaces + +The SDK provides interfaces for all gRPC services: + +- `SubscriberServiceClient` - Subscriber management +- `PaymentServiceClient` - Payment processing +- `UsageServiceClient` - Usage tracking +- `SystemServiceClient` - System monitoring + +## Development + +### Running Tests + +```bash +go test ./... +``` + +### Code Coverage + +```bash +go test -cover ./... +``` + +### Building + +```bash +go build ./... +``` + +## License + +This SDK is licensed under the MIT License. diff --git a/sdk/go/go.mod b/sdk/go/go.mod new file mode 100644 index 0000000..8b1c2e6 --- /dev/null +++ b/sdk/go/go.mod @@ -0,0 +1,3 @@ +module github.com/nutcas3/telecom-sdk-go + +go 1.26 diff --git a/sdk/go/telecom.go b/sdk/go/telecom.go new file mode 100644 index 0000000..4ba9a5a --- /dev/null +++ b/sdk/go/telecom.go @@ -0,0 +1,273 @@ +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 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 +} + +// DefaultConfig returns a default configuration +func DefaultConfig() *Config { + return &Config{ + APIURL: "http://localhost:8000", + APIKey: "", + Timeout: 30 * time.Second, + Retries: 3, + EnableHTTP: true, + } +} + +// NewClient creates a new Telecom SDK client +func NewClient(config *Config) (*Client, error) { + if config == nil { + config = DefaultConfig() + } + + client := &Client{ + config: config, + } + + // Initialize HTTP client + if config.EnableHTTP { + client.httpClient = &HTTPClient{ + client: &http.Client{ + Timeout: config.Timeout, + }, + apiKey: config.APIKey, + baseURL: config.APIURL, + } + } + + return client, nil +} + +// Close closes the client connections +func (c *Client) Close() error { + return nil +} + +// HTTP 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 +} + +// 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) +} diff --git a/sdk/go/types.go b/sdk/go/types.go new file mode 100644 index 0000000..6b4797b --- /dev/null +++ b/sdk/go/types.go @@ -0,0 +1,261 @@ +package telecom + +import ( + "context" + "time" +) + +// Subscriber represents a telecom subscriber +type Subscriber struct { + ID int64 `json:"id"` + IMSI string `json:"imsi"` + MSISDN string `json:"msisdn"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `json:"email"` + OrganizationID *string `json:"organization_id,omitempty"` + Status string `json:"status"` + PlanID int64 `json:"plan_id"` + Balance float64 `json:"balance"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// SubscriberList represents a paginated list of subscribers +type SubscriberList struct { + Subscribers []Subscriber `json:"subscribers"` + Total int64 `json:"total"` + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` + HasNext bool `json:"has_next"` + HasPrev bool `json:"has_prev"` +} + +// CreateSubscriberRequest represents a request to create a subscriber +type CreateSubscriberRequest struct { + IMSI string `json:"imsi"` + MSISDN string `json:"msisdn"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `json:"email"` + PlanID int64 `json:"plan_id"` + OrganizationID *string `json:"organization_id,omitempty"` +} + +// UpdateSubscriberRequest represents a request to update a subscriber +type UpdateSubscriberRequest struct { + FirstName *string `json:"first_name,omitempty"` + LastName *string `json:"last_name,omitempty"` + Email *string `json:"email,omitempty"` + PlanID *int64 `json:"plan_id,omitempty"` + Status *string `json:"status,omitempty"` +} + +// UsageStats represents usage statistics +type UsageStats struct { + SubscriberID string `json:"subscriber_id"` + DataUp int64 `json:"data_up"` + DataDown int64 `json:"data_down"` + VoiceSeconds int64 `json:"voice_seconds"` + SMSCount int64 `json:"sms_count"` + PeriodStart time.Time `json:"period_start"` + PeriodEnd time.Time `json:"period_end"` + Cost float64 `json:"cost"` +} + +// PaymentTransaction represents a payment transaction +type PaymentTransaction struct { + ID string `json:"id"` + SubscriberID string `json:"subscriber_id"` + Amount float64 `json:"amount"` + Currency string `json:"currency"` + Status string `json:"status"` + Gateway string `json:"gateway"` + TransactionID *string `json:"transaction_id,omitempty"` + CreatedAt time.Time `json:"created_at"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +// CreatePaymentRequest represents a request to create a payment +type CreatePaymentRequest struct { + SubscriberID string `json:"subscriber_id"` + Amount float64 `json:"amount"` + Currency string `json:"currency"` + Gateway string `json:"gateway"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +// SystemStats represents system statistics +type SystemStats struct { + ActiveSessions int64 `json:"active_sessions"` + TotalAccounts int64 `json:"total_accounts"` + BlockedUsers int64 `json:"blocked_users"` + LowBalanceAlerts int64 `json:"low_balance_alerts"` + Uptime float64 `json:"uptime"` + CPUUsage float64 `json:"cpu_usage"` + MemoryUsage float64 `json:"memory_usage"` +} + +// HealthStatus represents system health status +type HealthStatus struct { + Status string `json:"status"` + Timestamp time.Time `json:"timestamp"` + Checks map[string]interface{} `json:"checks"` + Uptime float64 `json:"uptime"` +} + +// RatingPlan represents a rating plan +type RatingPlan struct { + PlanID string `json:"plan_id"` + Name string `json:"name"` + DataRate float64 `json:"data_rate"` + VoiceRate float64 `json:"voice_rate"` + SMSRate float64 `json:"sms_rate"` + MonthlyFee float64 `json:"monthly_fee"` + DataLimit int64 `json:"data_limit"` + VoiceLimit int64 `json:"voice_limit"` + SMSLimit int64 `json:"sms_limit"` +} + +// UsageEvent represents a usage event +type UsageEvent struct { + ID string `json:"id"` + SubscriberID string `json:"subscriber_id"` + UsageType string `json:"usage_type"` + Amount int64 `json:"amount"` + Cost float64 `json:"cost"` + Timestamp time.Time `json:"timestamp"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +// CurrentSession represents a current active session +type CurrentSession struct { + SessionID string `json:"session_id"` + StartTime time.Time `json:"start_time"` + DataUp int64 `json:"data_up"` + DataDown int64 `json:"data_down"` + VoiceSeconds int64 `json:"voice_seconds"` + SMSCount int64 `json:"sms_count"` +} + +// RealTimeUsage represents real-time usage data +type RealTimeUsage struct { + CurrentSession *CurrentSession `json:"current_session,omitempty"` + TodayUsage map[string]int64 `json:"today_usage,omitempty"` +} + +// gRPC Service Interfaces + +// SubscriberServiceClient interface for gRPC subscriber service +type SubscriberServiceClient interface { + GetSubscriber(ctx context.Context, req *GetSubscriberRequest) (*Subscriber, error) + ListSubscribers(ctx context.Context, req *ListSubscribersRequest) (*SubscriberList, error) + CreateSubscriber(ctx context.Context, req *CreateSubscriberRequest) (*Subscriber, error) + UpdateSubscriber(ctx context.Context, req *UpdateSubscriberRequest) (*Subscriber, error) + DeleteSubscriber(ctx context.Context, req *DeleteSubscriberRequest) (*DeleteSubscriberResponse, error) +} + +// GetSubscriberRequest represents a gRPC request to get a subscriber +type GetSubscriberRequest struct { + Id int64 `json:"id"` +} + +// ListSubscribersRequest represents a gRPC request to list subscribers +type ListSubscribersRequest struct { + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` + Status string `json:"status,omitempty"` +} + +// DeleteSubscriberRequest represents a gRPC request to delete a subscriber +type DeleteSubscriberRequest struct { + Id int64 `json:"id"` +} + +// DeleteSubscriberResponse represents a gRPC response for delete subscriber +type DeleteSubscriberResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// PaymentServiceClient interface for gRPC payment service +type PaymentServiceClient interface { + CreatePayment(ctx context.Context, req *CreatePaymentRequest) (*PaymentTransaction, error) + GetPayment(ctx context.Context, req *GetPaymentRequest) (*PaymentTransaction, error) + ListPayments(ctx context.Context, req *ListPaymentsRequest) (*PaymentTransactionList, error) +} + +// GetPaymentRequest represents a gRPC request to get a payment +type GetPaymentRequest struct { + Id string `json:"id"` +} + +// ListPaymentsRequest represents a gRPC request to list payments +type ListPaymentsRequest struct { + SubscriberID string `json:"subscriber_id,omitempty"` + Status string `json:"status,omitempty"` + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` +} + +// PaymentTransactionList represents a list of payment transactions +type PaymentTransactionList struct { + Transactions []PaymentTransaction `json:"transactions"` + Total int64 `json:"total"` + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` + HasNext bool `json:"has_next"` + HasPrev bool `json:"has_prev"` +} + +// UsageServiceClient interface for gRPC usage service +type UsageServiceClient interface { + GetUsageStats(ctx context.Context, req *GetUsageStatsRequest) (*UsageStats, error) + GetRealTimeUsage(ctx context.Context, req *GetRealTimeUsageRequest) (*RealTimeUsage, error) + ListUsageEvents(ctx context.Context, req *ListUsageEventsRequest) (*UsageEventList, error) +} + +// GetUsageStatsRequest represents a gRPC request to get usage stats +type GetUsageStatsRequest struct { + SubscriberID int64 `json:"subscriber_id"` + StartDate time.Time `json:"start_date"` + EndDate time.Time `json:"end_date"` +} + +// GetRealTimeUsageRequest represents a gRPC request to get real-time usage +type GetRealTimeUsageRequest struct { + SubscriberID int64 `json:"subscriber_id"` +} + +// ListUsageEventsRequest represents a gRPC request to list usage events +type ListUsageEventsRequest struct { + SubscriberID *int64 `json:"subscriber_id,omitempty"` + UsageType string `json:"usage_type,omitempty"` + StartDate *time.Time `json:"start_date,omitempty"` + EndDate *time.Time `json:"end_date,omitempty"` + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` +} + +// UsageEventList represents a list of usage events +type UsageEventList struct { + Events []UsageEvent `json:"events"` + Total int64 `json:"total"` + Page int32 `json:"page"` + PageSize int32 `json:"page_size"` + HasNext bool `json:"has_next"` + HasPrev bool `json:"has_prev"` +} + +// SystemServiceClient interface for gRPC system service +type SystemServiceClient interface { + GetSystemStats(ctx context.Context, req *GetSystemStatsRequest) (*SystemStats, error) + GetHealthStatus(ctx context.Context, req *GetHealthStatusRequest) (*HealthStatus, error) +} + +// GetSystemStatsRequest represents a gRPC request to get system stats +type GetSystemStatsRequest struct{} + +// GetHealthStatusRequest represents a gRPC request to get health status +type GetHealthStatusRequest struct{}