diff --git a/sdk/elixir/README.md b/sdk/elixir/README.md new file mode 100644 index 0000000..d00535c --- /dev/null +++ b/sdk/elixir/README.md @@ -0,0 +1,257 @@ +# Telecom Platform Elixir SDK + +Elixir SDK for the Telecom Platform with GenServer-based async access and full pattern matching. + +## Features + +- **Modular Architecture**: Separate modules for authentication, HTTP client, and API services +- **GenServer Support**: Full GenServer-based async access +- **Pattern Matching**: Full Elixir pattern matching +- **Type Safety**: Struct-based data structures with typespecs +- **Error Handling**: Comprehensive error handling +- **Retry Logic**: Built-in retry mechanism with exponential backoff +- **OTP Integration**: Full OTP application support + +## Installation + +Add to your `mix.exs`: + +```elixir +defp deps do + [ + {:telecom_sdk, path: "../sdk/elixir"} + ] +end +``` + +Or install from source: + +```bash +cd sdk/elixir +mix deps.get +mix compile +``` + +## Architecture + +The SDK is organized into modular components: + +- `auth.ex` - Authentication provider handling API keys and JWT tokens +- `http_client.ex` - HTTP client with retry logic and authentication +- `subscribers_api.ex` - Subscriber management API +- `usage_api.ex` - Usage tracking API +- `payments_api.ex` - Payment processing API +- `rating_plans_api.ex` - Rating plan management API +- `system_api.ex` - System monitoring API +- `graphql_api.ex` - GraphQL query execution +- `telecom_sdk.ex` - Main SDK client integrating all modules + +## Quick Start + +```elixir +# Start the SDK +{:ok, _pid} = TelecomSDK.start_link( + api_url: "http://localhost:8000", + api_key: "your-api-key", + enable_logging: true +) + +# Get subscriber +{:ok, subscriber} = TelecomSDK.get_subscriber(1) +IO.puts("Subscriber: #{subscriber.first_name} #{subscriber.last_name}") + +# List subscribers +{:ok, list} = TelecomSDK.list_subscribers(1, 50, "active") +IO.puts("Total subscribers: #{list.total}") +``` + +## API Reference + +### Configuration + +```elixir +config = %TelecomSDK.Config{ + api_url: "http://localhost:8000", + api_key: "your-api-key", + jwt_secret: "your-jwt-secret", + timeout: 30_000, + max_retries: 3, + retry_delay: 1_000, + enable_logging: false +} +``` + +### Subscriber Management + +```elixir +# Get subscriber +{:ok, subscriber} = TelecomSDK.get_subscriber(1) + +# List subscribers +{:ok, list} = TelecomSDK.list_subscribers(1, 50, "active") + +# Create subscriber +{:ok, subscriber} = TelecomSDK.create_subscriber(%{ + imsi: "208930000000001", + msisdn: "+1234567890", + first_name: "John", + last_name: "Doe", + email: "john.doe@example.com", + plan_id: 1 +}) + +# Update subscriber +{:ok, subscriber} = TelecomSDK.update_subscriber(1, %{ + first_name: "Jane" +}) + +# Delete subscriber +{:ok, success} = TelecomSDK.delete_subscriber(1) + +# Suspend subscriber +{:ok, subscriber} = TelecomSDK.suspend_subscriber(1) + +# Activate subscriber +{:ok, subscriber} = TelecomSDK.activate_subscriber(1) +``` + +### Usage Management + +```elixir +# Get usage stats +start_date = DateTime.add(DateTime.utc_now(), -30, :day) +end_date = DateTime.utc_now() +{:ok, stats} = TelecomSDK.get_usage_stats(1, start_date, end_date) + +# List usage events +{:ok, events} = TelecomSDK.list_usage_events(%{ + subscriber_id: 1, + usage_type: "data", + page: 1, + page_size: 50 +}) + +# Get real-time usage +{:ok, realtime} = TelecomSDK.get_real_time_usage(1) +``` + +### Payment Management + +```elixir +# Create payment transaction +{:ok, transaction} = TelecomSDK.create_payment_transaction(%{ + subscriber_id: "1", + amount: 25.00, + currency: "USD", + gateway: "stripe", + metadata: %{description: "Monthly plan"} +}) + +# Get transaction +{:ok, transaction} = TelecomSDK.get_payment_transaction("txn-123") + +# List transactions +{:ok, transactions} = TelecomSDK.list_payment_transactions(%{ + subscriber_id: 1, + status: "completed", + page: 1, + page_size: 50 +}) +``` + +### Rating Plans + +```elixir +# List rating plans +{:ok, plans} = TelecomSDK.list_rating_plans() + +# Get specific plan +{:ok, plan} = TelecomSDK.get_rating_plan("plan-123") +``` + +### System Management + +```elixir +# Get system stats +{:ok, stats} = TelecomSDK.get_system_stats() + +# Get health status +{:ok, health} = TelecomSDK.get_health_status() +``` + +### GraphQL + +```elixir +# Execute GraphQL query +query = """ +query GetSubscribers($first: Int) { + subscribers(first: $first) { + id + firstName + lastName + } +} +""" + +variables = %{first: 10} +{:ok, result} = TelecomSDK.execute_graphql_query(query, variables) +``` + +## Authentication + +### JWT Token Generation + +```elixir +# Generate JWT token +{:ok, token} = TelecomSDK.generate_jwt_token("user-123", 24, %{custom_claim: "value"}) + +# Validate JWT token +{:ok, claims} = TelecomSDK.validate_jwt_token(token) +``` + +## Error Handling + +```elixir +case TelecomSDK.get_subscriber(1) do + {:ok, subscriber} -> + IO.puts("Subscriber: #{subscriber.first_name}") + {:error, :authentication_error} -> + IO.puts("Authentication failed") + {:error, :api_error} -> + IO.puts("API error") + {:error, :network_error} -> + IO.puts("Network error") + {:error, reason} -> + IO.puts("Error: #{inspect(reason)}") +end +``` + +## Development + +### Running Tests + +```bash +mix test +``` + +### Code Style + +```bash +mix format +mix compile --warnings-as-errors +``` + +### Building + +```bash +mix compile +``` + +## Requirements + +- Elixir 1.13+ +- OTP 26+ + +## License + +This SDK is licensed under the MIT License. diff --git a/sdk/elixir/lib/telecom_sdk.ex b/sdk/elixir/lib/telecom_sdk.ex index c3f03e0..017086a 100644 --- a/sdk/elixir/lib/telecom_sdk.ex +++ b/sdk/elixir/lib/telecom_sdk.ex @@ -1,7 +1,7 @@ defmodule TelecomSDK do @moduledoc """ Telecom Platform Elixir SDK - + Provides GenServer-based async access to the Telecom Platform API with full Elixir pattern matching. """ @@ -19,363 +19,96 @@ defmodule TelecomSDK do end @doc """ - Get subscriber by ID + Generate a JWT token for authentication """ - def get_subscriber(id) do - GenServer.call(__MODULE__, {:get_subscriber, id}) + def generate_jwt_token(user_id, expiry_hours \\ 24, additional_claims \\ %{}) do + GenServer.call(__MODULE__, {:generate_jwt_token, user_id, expiry_hours, additional_claims}) end @doc """ - List subscribers with pagination + Validate a JWT token """ - def list_subscribers(page \\ 1, page_size \\ 50, status \\ nil) do - GenServer.call(__MODULE__, {:list_subscribers, page, page_size, status}) + def validate_jwt_token(token) do + GenServer.call(__MODULE__, {:validate_jwt_token, token}) end @doc """ - Create a new subscriber + Close the SDK and cleanup resources """ - def create_subscriber(request) do - GenServer.call(__MODULE__, {:create_subscriber, request}) - end - - @doc """ - Update an existing subscriber - """ - def update_subscriber(id, request) do - GenServer.call(__MODULE__, {:update_subscriber, id, request}) - end - - @doc """ - Suspend a subscriber - """ - def suspend_subscriber(id) do - GenServer.call(__MODULE__, {:suspend_subscriber, id}) - end - - @doc """ - Activate a suspended subscriber - """ - def activate_subscriber(id) do - GenServer.call(__MODULE__, {:activate_subscriber, id}) - end - - @doc """ - Terminate a subscriber - """ - def terminate_subscriber(id) do - GenServer.call(__MODULE__, {:terminate_subscriber, id}) - end - - @doc """ - Get usage statistics for a subscriber - """ - def get_usage_stats(subscriber_id, start_date, end_date) do - GenServer.call(__MODULE__, {:get_usage_stats, subscriber_id, start_date, end_date}) - end - - @doc """ - List usage events with filtering - """ - def list_usage_events(opts \\ []) do - GenServer.call(__MODULE__, {:list_usage_events, opts}) - end - - @doc """ - Get real-time usage for a subscriber - """ - def get_real_time_usage(subscriber_id) do - GenServer.call(__MODULE__, {:get_real_time_usage, subscriber_id}) - end - - @doc """ - Create a payment transaction - """ - def create_payment_transaction(request) do - GenServer.call(__MODULE__, {:create_payment_transaction, request}) - end - - @doc """ - Get payment transaction by ID - """ - def get_payment_transaction(transaction_id) do - GenServer.call(__MODULE__, {:get_payment_transaction, transaction_id}) - end - - @doc """ - List payment transactions with filtering - """ - def list_payment_transactions(opts \\ []) do - GenServer.call(__MODULE__, {:list_payment_transactions, opts}) - end - - @doc """ - List all available rating plans - """ - def list_rating_plans do - GenServer.call(__MODULE__, :list_rating_plans) - end - - @doc """ - Get rating plan by ID - """ - def get_rating_plan(plan_id) do - GenServer.call(__MODULE__, {:get_rating_plan, plan_id}) - end - - @doc """ - Get system statistics - """ - def get_system_stats do - GenServer.call(__MODULE__, :get_system_stats) - end - - @doc """ - Get system health status - """ - def get_health_status do - GenServer.call(__MODULE__, :get_health_status) - end - - @doc """ - Execute a GraphQL query - """ - def execute_graphql_query(query, variables \\ %{}) do - GenServer.call(__MODULE__, {:execute_graphql_query, query, variables}) - end - - @doc """ - Connect to WebSocket for real-time updates - """ - def connect_websocket(message_handler \\ nil) do - GenServer.call(__MODULE__, {:connect_websocket, message_handler}) + def close do + GenServer.call(__MODULE__, :close) end # GenServer Callbacks @impl true def init(config) do - Finch.start_link(name: MyFinch) - {:ok, %{config: config, finch: MyFinch}} - end + auth_provider = AuthProvider.new(api_key: config.api_key, jwt_secret: config.jwt_secret) + http_client = HTTPClient.new(config.api_url, auth_provider, + timeout: config.timeout, + max_retries: config.max_retries, + retry_delay: config.retry_delay, + enable_logging: config.enable_logging + ) - @impl true - def handle_call({:get_subscriber, id}, _from, state) do - case make_request(:get, "/v1/subscribers/#{id}", nil, state) do - {:ok, response} -> {:reply, {:ok, Subscriber.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end + # Initialize API modules + subscribers = SubscriberAPI.new(http_client) + usage = UsageAPI.new(http_client) + payments = PaymentAPI.new(http_client) + rating_plans = RatingPlanAPI.new(http_client) + system = SystemAPI.new(http_client) + graphql = GraphQLAPI.new(http_client) - @impl true - def handle_call({:list_subscribers, page, page_size, status}, _from, state) do - params = %{page: page, page_size: page_size} - params = if status, do: Map.put(params, :status, status), else: params - - case make_request(:get, "/v1/subscribers", params, state) do - {:ok, response} -> {:reply, {:ok, SubscriberList.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call({:create_subscriber, request}, _from, state) do - case make_request(:post, "/v1/subscribers", request.to_map(), state) do - {:ok, response} -> {:reply, {:ok, Subscriber.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call({:update_subscriber, id, request}, _from, state) do - case make_request(:put, "/v1/subscribers/#{id}", request.to_map(), state) do - {:ok, response} -> {:reply, {:ok, Subscriber.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call({:suspend_subscriber, id}, _from, state) do - case make_request(:post, "/v1/subscribers/#{id}/suspend", nil, state) do - {:ok, response} -> {:reply, {:ok, Subscriber.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call({:activate_subscriber, id}, _from, state) do - case make_request(:post, "/v1/subscribers/#{id}/activate", nil, state) do - {:ok, response} -> {:reply, {:ok, Subscriber.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call({:terminate_subscriber, id}, _from, state) do - case make_request(:delete, "/v1/subscribers/#{id}", nil, state) do - {:ok, response} -> {:reply, {:ok, response["success"]}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call({:get_usage_stats, subscriber_id, start_date, end_date}, _from, state) do - params = %{ - start_date: DateTime.to_iso8601(start_date), - end_date: DateTime.to_iso8601(end_date) + state = %{ + config: config, + auth_provider: auth_provider, + http_client: http_client, + subscribers: subscribers, + usage: usage, + payments: payments, + rating_plans: rating_plans, + system: system, + graphql: graphql } - - case make_request(:get, "/v1/subscribers/#{subscriber_id}/usage", params, state) do - {:ok, response} -> {:reply, {:ok, UsageStats.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end + + {:ok, state} end @impl true - def handle_call({:list_usage_events, opts}, _from, state) do - params = build_params(opts, [:subscriber_id, :usage_type, :start_date, :end_date, :page, :page_size]) - - case make_request(:get, "/v1/usage/events", params, state) do - {:ok, response} -> {:reply, {:ok, UsageEventList.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end + def handle_call({:generate_jwt_token, user_id, expiry_hours, additional_claims}, _from, state) do + auth_provider = AuthProvider.generate_jwt_token(state.auth_provider, user_id, expiry_hours, additional_claims) + {:reply, {:ok, auth_provider.token_cache}, %{state | auth_provider: auth_provider}} end @impl true - def handle_call({:get_real_time_usage, subscriber_id}, _from, state) do - case make_request(:get, "/v1/subscribers/#{subscriber_id}/realtime", nil, state) do - {:ok, response} -> {:reply, {:ok, RealTimeUsage.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end + def handle_call({:validate_jwt_token, token}, _from, state) do + result = AuthProvider.validate_jwt_token(state.auth_provider, token) + {:reply, {:ok, result}, state} end @impl true - def handle_call({:create_payment_transaction, request}, _from, state) do - case make_request(:post, "/v1/payments/transactions", request.to_map(), state) do - {:ok, response} -> {:reply, {:ok, PaymentTransaction.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end + def handle_call(:close, _from, state) do + HTTPClient.close(state.http_client) + {:reply, :ok, state} end - @impl true - def handle_call({:get_payment_transaction, transaction_id}, _from, state) do - case make_request(:get, "/v1/payments/transactions/#{transaction_id}", nil, state) do - {:ok, response} -> {:reply, {:ok, PaymentTransaction.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end + # Configuration - @impl true - def handle_call({:list_payment_transactions, opts}, _from, state) do - params = build_params(opts, [:subscriber_id, :status, :page, :page_size]) - - case make_request(:get, "/v1/payments/transactions", params, state) do - {:ok, response} -> {:reply, {:ok, PaymentTransactionList.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end + defstruct [:api_url, :api_key, :jwt_secret, :timeout, :max_retries, :retry_delay, :enable_logging] - @impl true - def handle_call(:list_rating_plans, _from, state) do - case make_request(:get, "/v1/rating-plans", nil, state) do - {:ok, response} -> - plans = Enum.map(response, &RatingPlan.from_map/1) - {:reply, {:ok, plans}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end + defmodule Config do + @moduledoc """ + Configuration for Telecom SDK + """ - @impl true - def handle_call({:get_rating_plan, plan_id}, _from, state) do - case make_request(:get, "/v1/rating-plans/#{plan_id}", nil, state) do - {:ok, response} -> {:reply, {:ok, RatingPlan.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call(:get_system_stats, _from, state) do - case make_request(:get, "/v1/system/stats", nil, state) do - {:ok, response} -> {:reply, {:ok, SystemStats.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call(:get_health_status, _from, state) do - case make_request(:get, "/v1/health", nil, state) do - {:ok, response} -> {:reply, {:ok, HealthStatus.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call({:execute_graphql_query, query, variables}, _from, state) do - request = %{query: query, variables: variables} - - case make_request(:post, "/graphql", request, state) do - {:ok, response} -> {:reply, {:ok, GraphQLResponse.from_map(response)}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - @impl true - def handle_call({:connect_websocket, message_handler}, _from, state) do - ws_url = String.replace(state.config.api_url, "http://", "ws://") <> "/ws" - - case WebSockex.start_link(ws_url, WebSocketHandler, %{message_handler: message_handler}) do - {:ok, pid} -> {:reply, {:ok, pid}, state} - {:error, error} -> {:reply, {:error, error}, state} - end - end - - # Private functions - - defp make_request(method, path, params, state) do - url = state.config.api_url <> path - - headers = [ - {"user-agent", "Telecom-Elixir-SDK/1.0.0"}, - {"content-type", "application/json"} + defstruct [ + api_url: "http://localhost:8000", + api_key: nil, + jwt_secret: nil, + timeout: 30_000, + max_retries: 3, + retry_delay: 1_000, + enable_logging: false ] - - headers = if state.config.api_key do - [{"authorization", "Bearer #{state.config.api_key}"} | headers] - else - headers - end - - request = Finch.build(method, url, headers, Jason.encode!(params || %{})) - - case Finch.request(request, state.finch) do - {:ok, response} -> - case response.status do - 200 -> - body = response.body |> Jason.decode!() - {:ok, body} - 401 -> - {:error, :authentication_error} - 429 -> - {:error, :rate_limit_error} - status when status in 400..499 -> - body = response.body |> Jason.decode!() - {:error, {:api_error, body["error"] || "Bad request"}} - status when status in 500..599 -> - {:error, {:server_error, status}} - _ -> - {:error, :unknown_error} - end - {:error, reason} -> - {:error, {:network_error, reason}} - end - end - - defp build_params(opts, keys) do - Enum.reduce(keys, %{}, fn key, acc -> - case Map.get(opts, key) do - nil -> acc - value -> Map.put(acc, key, value) - end - end) end end diff --git a/sdk/elixir/lib/telecom_sdk/auth.ex b/sdk/elixir/lib/telecom_sdk/auth.ex new file mode 100644 index 0000000..36d05be --- /dev/null +++ b/sdk/elixir/lib/telecom_sdk/auth.ex @@ -0,0 +1,104 @@ +defmodule TelecomSDK.AuthProvider do + @moduledoc """ + Authentication provider for the Telecom SDK + """ + + defstruct [:api_key, :jwt_secret, :token_cache, :token_expiry] + + def new(opts \\ []) do + %__MODULE__{ + api_key: Keyword.get(opts, :api_key), + jwt_secret: Keyword.get(opts, :jwt_secret), + token_cache: nil, + token_expiry: nil + } + end + + def get_headers(%__MODULE__{api_key: api_key, token_cache: token_cache, token_expiry: token_expiry}) do + headers = %{ + "Content-Type" => "application/json", + "User-Agent" => "Telecom-Elixir-SDK/1.0.0" + } + + headers = if api_key, do: Map.put(headers, "X-API-Key", api_key), else: headers + + if token_cache && token_valid?(token_expiry) do + Map.put(headers, "Authorization", "Bearer #{token_cache}") + else + headers + end + end + + def generate_jwt_token(%__MODULE__{jwt_secret: jwt_secret} = auth, user_id, expiry_hours \\ 24, additional_claims \\ %{}) do + unless jwt_secret, do: raise(ArgumentError, "JWT secret not configured") + + now = System.system_time(:second) + exp = now + (expiry_hours * 3600) + + claims = %{ + "sub" => user_id, + "exp" => exp, + "iat" => now + } + |> Map.merge(additional_claims) + + header = %{"alg" => "HS256", "typ" => "JWT"} + + encoded_header = base64_url_encode(header) + encoded_payload = base64_url_encode(claims) + signature = sign("#{encoded_header}.#{encoded_payload}", jwt_secret) + + token = "#{encoded_header}.#{encoded_payload}.#{signature}" + + %{auth | token_cache: token, token_expiry: exp} + end + + def validate_jwt_token(%__MODULE__{jwt_secret: jwt_secret}, token) do + unless jwt_secret, do: raise(ArgumentError, "JWT secret not configured") + + parts = String.split(token, ".") + unless length(parts) == 3, do: raise(ArgumentError, "Invalid token format") + + [encoded_header, encoded_payload, signature] = parts + expected_signature = sign("#{encoded_header}.#{encoded_payload}", jwt_secret) + + unless signature == expected_signature, do: raise(ArgumentError, "Invalid token signature") + + case base64_url_decode(encoded_payload) do + {:ok, payload_bytes} -> + case Jason.decode(payload_bytes) do + {:ok, payload} -> + if payload["exp"] && payload["exp"] < System.system_time(:second) do + raise(ArgumentError, "Token has expired") + end + payload + {:error, _} -> raise(ArgumentError, "Failed to decode payload") + end + {:error, _} -> raise(ArgumentError, "Failed to decode payload") + end + end + + def clear_token_cache(auth) do + %{auth | token_cache: nil, token_expiry: nil} + end + + defp token_valid?(nil), do: false + defp token_valid?(expiry), do: System.system_time(:second) < expiry + + defp base64_url_encode(data) do + json = Jason.encode!(data) + json + |> Base.url_encode64(padding: false) + end + + defp base64_url_decode(data) do + # Add padding if needed + padded_data = data <> String.duplicate("=", rem(4 - rem(String.length(data), 4), 4)) + Base.url_decode64(padded_data) + end + + defp sign(data, secret) do + :crypto.mac(:hmac, :sha256, secret, data) + |> Base.url_encode64(padding: false) + end +end diff --git a/sdk/elixir/lib/telecom_sdk/graphql_api.ex b/sdk/elixir/lib/telecom_sdk/graphql_api.ex new file mode 100644 index 0000000..1b83981 --- /dev/null +++ b/sdk/elixir/lib/telecom_sdk/graphql_api.ex @@ -0,0 +1,17 @@ +defmodule TelecomSDK.GraphQLAPI do + @moduledoc """ + API for GraphQL queries + """ + + defstruct [:client] + + def new(client) do + %__MODULE__{client: client} + end + + def execute(api, query, variables \\ nil) do + request = %{query: query} + request = if variables, do: Map.put(request, :variables, variables), else: request + TelecomSDK.HTTPClient.post(api.client, "/graphql", request) + end +end diff --git a/sdk/elixir/lib/telecom_sdk/http_client.ex b/sdk/elixir/lib/telecom_sdk/http_client.ex new file mode 100644 index 0000000..3ccc8bf --- /dev/null +++ b/sdk/elixir/lib/telecom_sdk/http_client.ex @@ -0,0 +1,84 @@ +defmodule TelecomSDK.HTTPClient do + @moduledoc """ + HTTP client for making API requests + """ + + defstruct [:base_url, :auth_provider, :timeout, :max_retries, :retry_delay, :enable_logging] + + def new(base_url, auth_provider, opts \\ []) do + %__MODULE__{ + base_url: base_url, + auth_provider: auth_provider, + timeout: Keyword.get(opts, :timeout, 30_000), + max_retries: Keyword.get(opts, :max_retries, 3), + retry_delay: Keyword.get(opts, :retry_delay, 1_000), + enable_logging: Keyword.get(opts, :enable_logging, false) + } + end + + def get(client, path, params \\ %{}) do + request(client, "GET", path, nil, params) + end + + def post(client, path, body \\ nil) do + request(client, "POST", path, body, nil) + end + + def put(client, path, body \\ nil) do + request(client, "PUT", path, body, nil) + end + + def delete(client, path) do + request(client, "DELETE", path, nil, nil) + end + + defp request(client, method, path, body, params) do + url = client.base_url <> path + headers = TelecomSDK.AuthProvider.get_headers(client.auth_provider) + + opts = [ + timeout: client.timeout, + recv_timeout: client.timeout + ] + + opts = if params != %{}, do: Keyword.put(opts, :params, params), else: opts + opts = if body, do: Keyword.put(opts, :body, Jason.encode!(body)), else: opts + + last_error = nil + + Enum.reduce_while(0..client.max_retries, nil, fn attempt, _acc -> + case do_request(method, url, headers, opts) do + {:ok, response} -> + case handle_response_errors(response) do + :ok -> {:halt, {:ok, Jason.decode!(response.body)}} + {:error, error} -> {:halt, {:error, error}} + end + {:error, error} -> + if attempt < client.max_retries do + Process.sleep(client.retry_delay * round(:math.pow(2, attempt))) + {:cont, error} + else + {:halt, {:error, error}} + end + end + end) + end + + defp do_request(method, url, headers, opts) do + case HTTPoison.request(method, url, Keyword.get(opts, :body), headers, opts) do + {:ok, response} -> {:ok, response} + {:error, error} -> {:error, error} + end + end + + defp handle_response_errors(%{status_code: status}) when status >= 200 and status < 300, do: :ok + defp handle_response_errors(%{status_code: 401}), do: {:error, :authentication_failed} + defp handle_response_errors(%{status_code: 429}), do: {:error, :rate_limit_exceeded} + defp handle_response_errors(%{status_code: status}) when status >= 400 and status < 500, do: {:error, {:client_error, status}} + defp handle_response_errors(%{status_code: status}) when status >= 500, do: {:error, {:server_error, status}} + + def close(_client) do + # HTTPoison doesn't need explicit cleanup + :ok + end +end diff --git a/sdk/elixir/lib/telecom_sdk/payments_api.ex b/sdk/elixir/lib/telecom_sdk/payments_api.ex new file mode 100644 index 0000000..02bb0bb --- /dev/null +++ b/sdk/elixir/lib/telecom_sdk/payments_api.ex @@ -0,0 +1,31 @@ +defmodule TelecomSDK.PaymentAPI do + @moduledoc """ + API for payment management + """ + + defstruct [:client] + + def new(client) do + %__MODULE__{client: client} + end + + def create_transaction(api, request) do + TelecomSDK.HTTPClient.post(api.client, "/v1/payments/transactions", request) + end + + def get_transaction(api, transaction_id) do + TelecomSDK.HTTPClient.get(api.client, "/v1/payments/transactions/#{transaction_id}") + end + + def list_transactions(api, opts \\ []) do + params = %{ + page: Keyword.get(opts, :page, 1), + page_size: Keyword.get(opts, :page_size, 50) + } + + params = if Keyword.get(opts, :subscriber_id), do: Map.put(params, :subscriber_id, Keyword.get(opts, :subscriber_id)), else: params + params = if Keyword.get(opts, :status), do: Map.put(params, :status, Keyword.get(opts, :status)), else: params + + TelecomSDK.HTTPClient.get(api.client, "/v1/payments/transactions", params) + end +end diff --git a/sdk/elixir/lib/telecom_sdk/rating_plans_api.ex b/sdk/elixir/lib/telecom_sdk/rating_plans_api.ex new file mode 100644 index 0000000..b9f678d --- /dev/null +++ b/sdk/elixir/lib/telecom_sdk/rating_plans_api.ex @@ -0,0 +1,19 @@ +defmodule TelecomSDK.RatingPlanAPI do + @moduledoc """ + API for rating plan management + """ + + defstruct [:client] + + def new(client) do + %__MODULE__{client: client} + end + + def list(api) do + TelecomSDK.HTTPClient.get(api.client, "/v1/rating-plans") + end + + def get(api, plan_id) do + TelecomSDK.HTTPClient.get(api.client, "/v1/rating-plans/#{plan_id}") + end +end diff --git a/sdk/elixir/lib/telecom_sdk/subscribers_api.ex b/sdk/elixir/lib/telecom_sdk/subscribers_api.ex new file mode 100644 index 0000000..0567f17 --- /dev/null +++ b/sdk/elixir/lib/telecom_sdk/subscribers_api.ex @@ -0,0 +1,41 @@ +defmodule TelecomSDK.SubscriberAPI do + @moduledoc """ + API for subscriber management + """ + + defstruct [:client] + + def new(client) do + %__MODULE__{client: client} + end + + def get(api, id) do + TelecomSDK.HTTPClient.get(api.client, "/v1/subscribers/#{id}") + end + + def list(api, page \\ 1, page_size \\ 50, status \\ nil) do + params = %{page: page, page_size: page_size} + params = if status, do: Map.put(params, :status, status), else: params + TelecomSDK.HTTPClient.get(api.client, "/v1/subscribers", params) + end + + def create(api, request) do + TelecomSDK.HTTPClient.post(api.client, "/v1/subscribers", request) + end + + def update(api, id, request) do + TelecomSDK.HTTPClient.put(api.client, "/v1/subscribers/#{id}", request) + end + + def delete(api, id) do + TelecomSDK.HTTPClient.delete(api.client, "/v1/subscribers/#{id}") + end + + def suspend(api, id) do + TelecomSDK.HTTPClient.post(api.client, "/v1/subscribers/#{id}/suspend") + end + + def activate(api, id) do + TelecomSDK.HTTPClient.post(api.client, "/v1/subscribers/#{id}/activate") + end +end diff --git a/sdk/elixir/lib/telecom_sdk/system_api.ex b/sdk/elixir/lib/telecom_sdk/system_api.ex new file mode 100644 index 0000000..0a10d90 --- /dev/null +++ b/sdk/elixir/lib/telecom_sdk/system_api.ex @@ -0,0 +1,19 @@ +defmodule TelecomSDK.SystemAPI do + @moduledoc """ + API for system management + """ + + defstruct [:client] + + def new(client) do + %__MODULE__{client: client} + end + + def get_stats(api) do + TelecomSDK.HTTPClient.get(api.client, "/v1/system/stats") + end + + def get_health(api) do + TelecomSDK.HTTPClient.get(api.client, "/v1/health") + end +end diff --git a/sdk/elixir/lib/telecom_sdk/usage_api.ex b/sdk/elixir/lib/telecom_sdk/usage_api.ex new file mode 100644 index 0000000..9c3e1b2 --- /dev/null +++ b/sdk/elixir/lib/telecom_sdk/usage_api.ex @@ -0,0 +1,37 @@ +defmodule TelecomSDK.UsageAPI do + @moduledoc """ + API for usage management + """ + + defstruct [:client] + + def new(client) do + %__MODULE__{client: client} + end + + def get_stats(api, subscriber_id, start_date, end_date) do + params = %{ + start_date: DateTime.to_iso8601(start_date), + end_date: DateTime.to_iso8601(end_date) + } + TelecomSDK.HTTPClient.get(api.client, "/v1/subscribers/#{subscriber_id}/usage", params) + end + + def list_events(api, opts \\ []) do + params = %{ + page: Keyword.get(opts, :page, 1), + page_size: Keyword.get(opts, :page_size, 50) + } + + params = if Keyword.get(opts, :subscriber_id), do: Map.put(params, :subscriber_id, Keyword.get(opts, :subscriber_id)), else: params + params = if Keyword.get(opts, :usage_type), do: Map.put(params, :usage_type, Keyword.get(opts, :usage_type)), else: params + params = if Keyword.get(opts, :start_date), do: Map.put(params, :start_date, DateTime.to_iso8601(Keyword.get(opts, :start_date))), else: params + params = if Keyword.get(opts, :end_date), do: Map.put(params, :end_date, DateTime.to_iso8601(Keyword.get(opts, :end_date))), else: params + + TelecomSDK.HTTPClient.get(api.client, "/v1/usage/events", params) + end + + def get_realtime(api, subscriber_id) do + TelecomSDK.HTTPClient.get(api.client, "/v1/subscribers/#{subscriber_id}/realtime") + end +end