Files
telecom-platform/apps/cli/internal/api/client.go
T
nutcas3 d4ee90de45 feat: Add BaseURL method to API client for exposing configured endpoint
- Add BaseURL() method to return configured API endpoint
- Add godoc comment describing use case for UI display
2026-04-17 16:32:00 +03:00

420 lines
11 KiB
Go

package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/nutcas3/telecom-platform/apps/cli/internal/types"
)
// Client represents the API client for the telecom platform
type Client struct {
baseURL string
apiKey string
httpClient *http.Client
}
// NewClient creates a new API client from CLI config
func NewClient(cfg *types.CLIConfig) *Client {
if cfg == nil {
cfg = &types.CLIConfig{APIEndpoint: "http://localhost:8000"}
}
return &Client{
baseURL: cfg.APIEndpoint,
apiKey: cfg.APIToken,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// BaseURL returns the configured API endpoint (useful for UI display).
func (c *Client) BaseURL() string {
return c.baseURL
}
// doGetJSON performs a GET request and decodes JSON into out
func (c *Client) doGetJSON(path string, out any) error {
req, err := http.NewRequest("GET", c.baseURL+path, nil)
if err != nil {
return err
}
if c.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+c.apiKey)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API %s %d: %s", path, resp.StatusCode, string(body))
}
return json.NewDecoder(resp.Body).Decode(out)
}
// doPostJSON performs a POST request with JSON body and decodes response
func (c *Client) doPostJSON(path string, body any, out any) error {
var buf *bytes.Buffer
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return err
}
buf = bytes.NewBuffer(b)
} else {
buf = bytes.NewBuffer(nil)
}
req, err := http.NewRequest("POST", c.baseURL+path, buf)
if err != nil {
return err
}
if c.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+c.apiKey)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
rb, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API %s %d: %s", path, resp.StatusCode, string(rb))
}
if out == nil {
return nil
}
return json.NewDecoder(resp.Body).Decode(out)
}
// doDelete performs a DELETE request
func (c *Client) doDelete(path string) error {
req, err := http.NewRequest("DELETE", c.baseURL+path, nil)
if err != nil {
return err
}
if c.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+c.apiKey)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("API %s %d: %s", path, resp.StatusCode, string(body))
}
return nil
}
// SubscriberAccount holds account info
type SubscriberAccount struct {
IMSI string `json:"imsi"`
Name string `json:"name"`
Status string `json:"status"`
Balance float64 `json:"balance"`
}
// ListSubscribers retrieves subscribers
func (c *Client) ListSubscribers() ([]SubscriberAccount, error) {
var resp struct {
Data []SubscriberAccount `json:"data"`
}
if err := c.doGetJSON("/api/v1/subscribers", &resp); err != nil {
return nil, err
}
return resp.Data, nil
}
// GetSubscriber retrieves a single subscriber
func (c *Client) GetSubscriber(imsi string) (*SubscriberAccount, error) {
var sub SubscriberAccount
if err := c.doGetJSON("/api/v1/subscribers/"+imsi, &sub); err != nil {
return nil, err
}
return &sub, nil
}
// CreateSubscriber creates a subscriber
func (c *Client) CreateSubscriber(imsi, name string) (*SubscriberAccount, error) {
var sub SubscriberAccount
body := map[string]string{"imsi": imsi, "name": name}
if err := c.doPostJSON("/api/v1/subscribers", body, &sub); err != nil {
return nil, err
}
return &sub, nil
}
// DeleteSubscriber deletes a subscriber
func (c *Client) DeleteSubscriber(imsi string) error {
return c.doDelete("/api/v1/subscribers/" + imsi)
}
// Service describes a platform service
type Service struct {
Name string `json:"name"`
Status string `json:"status"`
Version string `json:"version"`
Uptime string `json:"uptime"`
CPU float64 `json:"cpu"`
Memory string `json:"memory"`
}
// PostRestart requests the API to restart the named service.
func (c *Client) PostRestart(name string) error {
return c.doPostJSON("/api/v1/services/"+name+"/restart", nil, nil)
}
// ListServices returns platform services
func (c *Client) ListServices() ([]Service, error) {
var resp struct {
Data []Service `json:"data"`
}
if err := c.doGetJSON("/api/v1/services", &resp); err != nil {
return nil, err
}
return resp.Data, nil
}
// Alert represents a monitoring alert
type Alert struct {
ID string `json:"id"`
Severity string `json:"severity"`
Service string `json:"service"`
Message string `json:"message"`
Time time.Time `json:"time"`
}
// ListAlerts returns alerts
func (c *Client) ListAlerts() ([]Alert, error) {
var resp struct {
Data []Alert `json:"data"`
}
if err := c.doGetJSON("/api/v1/alerts", &resp); err != nil {
return nil, err
}
return resp.Data, nil
}
// SystemStats represents system health/metrics
type SystemStats struct {
ActiveSessions int `json:"active_sessions"`
TotalAccounts int `json:"total_accounts"`
BlockedUsers int `json:"blocked_users"`
LowBalanceAlerts int `json:"low_balance_alerts"`
CPUUsage float64 `json:"cpu_usage"`
MemoryUsage float64 `json:"memory_usage"`
Uptime string `json:"uptime"`
}
// GetSystemStats retrieves system stats
func (c *Client) GetSystemStats() (*SystemStats, error) {
var stats SystemStats
if err := c.doGetJSON("/api/v1/system/stats", &stats); err != nil {
return nil, err
}
return &stats, nil
}
// HealthStatus represents a health check result
type HealthStatus struct {
Service string `json:"service"`
Status string `json:"status"`
Uptime string `json:"uptime"`
LastCheck time.Time `json:"last_check"`
}
// GetHealth returns per-service health
func (c *Client) GetHealth() ([]HealthStatus, error) {
var resp struct {
Data []HealthStatus `json:"data"`
}
if err := c.doGetJSON("/api/v1/health", &resp); err != nil {
return nil, err
}
return resp.Data, nil
}
// Invoice represents an invoice from the API
type Invoice struct {
ID string `json:"id"`
SubscriberID string `json:"subscriber_id"`
Amount float64 `json:"amount"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
DueDate time.Time `json:"due_date"`
PaidAt *time.Time `json:"paid_at,omitempty"`
Subscriber Subscriber `json:"subscriber"`
}
// Payment represents a payment from the API
type Payment struct {
ID string `json:"id"`
InvoiceID string `json:"invoice_id"`
Amount float64 `json:"amount"`
Method string `json:"method"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
ProcessedAt *time.Time `json:"processed_at,omitempty"`
}
// Subscriber represents a subscriber from the API
type Subscriber struct {
ID uint `json:"id"`
IMSI string `json:"imsi"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Status string `json:"status"`
}
// GetInvoices retrieves invoices from the API
func (c *Client) GetInvoices() ([]Invoice, error) {
url := fmt.Sprintf("%s/api/v1/invoices", c.baseURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to make request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
var response struct {
Data []Invoice `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return response.Data, nil
}
// GetPayments retrieves payments from the API
func (c *Client) GetPayments() ([]Payment, error) {
url := fmt.Sprintf("%s/api/v1/payments", c.baseURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to make request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
var response struct {
Data []Payment `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return response.Data, nil
}
// GenerateInvoiceRequest represents a request to generate an invoice
type GenerateInvoiceRequest struct {
SubscriberID string `json:"subscriber_id"`
}
// GenerateInvoiceResponse represents a response from generating an invoice
type GenerateInvoiceResponse struct {
ID string `json:"id"`
Amount float64 `json:"amount"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
DueDate time.Time `json:"due_date"`
}
// GenerateInvoice generates a new invoice for a subscriber
func (c *Client) GenerateInvoice(subscriberID string) (*GenerateInvoiceResponse, error) {
url := fmt.Sprintf("%s/api/v1/invoices/generate", c.baseURL)
request := GenerateInvoiceRequest{
SubscriberID: subscriberID,
}
jsonData, err := json.Marshal(request)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to make request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
}
var response GenerateInvoiceResponse
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return &response, nil
}
// IsConnected checks if the API server is reachable
func (c *Client) IsConnected() bool {
url := fmt.Sprintf("%s/health", c.baseURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return false
}
req.Header.Set("Authorization", "Bearer "+c.apiKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
return resp.StatusCode == http.StatusOK
}