telphin_api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ module TelphinApi
2
+ # A module containing the methods for authorization.
3
+ #
4
+ # @note `TelphinApi::Authorization` extends `TelphinApi` so these methods should be called from the latter.
5
+ module Authorization
6
+ # Authorization options.
7
+ OPTIONS = {
8
+ client: {
9
+ token_url: '/oauth/token.php'
10
+ },
11
+ client_credentials: {
12
+ 'auth_scheme' => 'request_body'
13
+ }
14
+ }
15
+
16
+ # Not used for this strategy
17
+ #
18
+ # @raise [NotImplementedError]
19
+ def authorization_url
20
+ fail(NotImplementedError, 'The authorization endpoint is not used in this strategy')
21
+ end
22
+
23
+ # Authorization (getting the access token and building a `TelphinApi::Client` with it).
24
+ # @raise [ArgumentError] raises after receiving an unknown authorization type.
25
+ # @return [TelphinApi::Client] An API client.
26
+ def authorize(options = {})
27
+ options[:client_id] ||= TelphinApi.app_key
28
+ options[:client_secret] ||= TelphinApi.app_secret
29
+ token = client.client_credentials.get_token(options, OPTIONS[:client_credentials].dup)
30
+ Client.new(token)
31
+ end
32
+
33
+ private
34
+ def client
35
+ @client ||= OAuth2::Client.new(TelphinApi.app_key, TelphinApi.app_secret, {site: TelphinApi.site}.merge(OPTIONS[:client]))
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,75 @@
1
+ module TelphinApi
2
+ # A class representing a connection to Telphin. It holds the access token.
3
+ class Client
4
+ include Resolver
5
+
6
+ # Значение маркера доступа. Это значение используется при выполнении запросов к API.
7
+ # @return [String]
8
+ attr_reader :token
9
+
10
+ # Тип маркера. Допустимо только значение Bearer.
11
+ # @return [String]
12
+ attr_reader :token_type
13
+
14
+ # Новый маркер, который можно использовать для обновления маркера с истекшим сроком действия.
15
+ # @return [String]
16
+ attr_reader :refresh_token
17
+
18
+ # Срок действия маркера доступа (в секундах).
19
+ # @return [Time]
20
+ attr_reader :expires_in
21
+
22
+ # A new API client.
23
+ # If given an `OAuth2::AccessToken` instance, it extracts and keeps
24
+ # the token string, the user id and the expiration time;
25
+ # otherwise it just stores the given token.
26
+ # @param [String, OAuth2::AccessToken] token An access token.
27
+ def initialize(token = nil)
28
+ if token.respond_to?(:token) && token.respond_to?(:params)
29
+ # token is an OAuth2::AccessToken
30
+ @token = token.token
31
+ @token_type = token.params['token_type']
32
+ @refresh_token = token.params['refresh_token']
33
+ @expires_in = Time.now + token.expires_in unless token.expires_in.nil?
34
+ else
35
+ # token is a String or nil
36
+ @token = token
37
+ end
38
+ end
39
+
40
+ # Is a `TelphinApi::Client` instance authorized.
41
+ def authorized?
42
+ !@token.nil?
43
+ end
44
+
45
+ # Did the token already expire.
46
+ def expired?
47
+ @expires_in && @expires_in < Time.now
48
+ end
49
+
50
+ # Called without arguments it returns the `execute` namespace;
51
+ # called with arguments it calls the top-level `execute` API method.
52
+ def execute(*args)
53
+ if args.empty?
54
+ create_namespace(:execute)
55
+ else
56
+ call_method([:execute, *args])
57
+ end
58
+ end
59
+
60
+ # If the called method is a namespace, it creates and returns a new `TelphinApi::Namespace` instance.
61
+ # Otherwise it creates a `TelphinApi::Method` instance and calls it passing the arguments and a block.
62
+ def method_missing(*args, &block)
63
+ if Namespace.exists?(args.first)
64
+ create_namespace(args.first)
65
+ else
66
+ call_method(args, &block)
67
+ end
68
+ end
69
+
70
+ private
71
+ def settings
72
+ @settings ||= self.get_user_settings
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,75 @@
1
+ require 'logger'
2
+
3
+ module TelphinApi
4
+ # General configuration module.
5
+ #
6
+ # @note `TelphinApi::Configuration` extends `TelphinApi` so these methods should be called from the latter.
7
+ module Configuration
8
+ # Available options.
9
+ OPTION_NAMES = [
10
+ :app_key,
11
+ :app_secret,
12
+ :site,
13
+ :adapter,
14
+ :faraday_options,
15
+ :max_retries,
16
+ :logger,
17
+ :log_requests,
18
+ :log_errors,
19
+ :log_responses,
20
+ ]
21
+
22
+ attr_accessor *OPTION_NAMES
23
+
24
+ alias_method :log_requests?, :log_requests
25
+ alias_method :log_errors?, :log_errors
26
+ alias_method :log_responses?, :log_responses
27
+
28
+ # Default HTTP adapter.
29
+ DEFAULT_ADAPTER = Faraday.default_adapter
30
+
31
+ # Default HTTP verb for API methods.
32
+ DEFAULT_HTTP_VERB = :post
33
+
34
+ # Default max retries count.
35
+ DEFAULT_MAX_RETRIES = 2
36
+
37
+ # Logger default options.
38
+ DEFAULT_LOGGER_OPTIONS = {
39
+ requests: true,
40
+ errors: true,
41
+ responses: false
42
+ }
43
+
44
+ # Default url for site api
45
+ DEFAULT_URL = 'https://pbx.telphin.ru/uapi'
46
+
47
+ # A global configuration set via the block.
48
+ # @example
49
+ # TelphinApi.configure do |config|
50
+ # config.adapter = :net_http
51
+ # config.logger = Rails.logger
52
+ # end
53
+ def configure
54
+ yield self if block_given?
55
+ self
56
+ end
57
+
58
+ # Reset all configuration options to defaults.
59
+ def reset
60
+ @site = DEFAULT_URL
61
+ @adapter = DEFAULT_ADAPTER
62
+ @faraday_options = {}
63
+ @max_retries = DEFAULT_MAX_RETRIES
64
+ @logger = ::Logger.new(STDOUT)
65
+ @log_requests = DEFAULT_LOGGER_OPTIONS[:requests]
66
+ @log_errors = DEFAULT_LOGGER_OPTIONS[:errors]
67
+ @log_responses = DEFAULT_LOGGER_OPTIONS[:responses]
68
+ end
69
+
70
+ # When this module is extended, set all configuration options to their default values.
71
+ def self.extended(base)
72
+ base.reset
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,21 @@
1
+ module TelphinApi
2
+ # An exception raised by `TelphinApi::Result` when given a response with an error.
3
+ class Error < StandardError
4
+ # An error code.
5
+ # @return [String]
6
+ attr_reader :error_code
7
+
8
+ # An exception is initialized by the data from response mash.
9
+ # @param [Hash] data Error data.
10
+ def initialize(data)
11
+ @error_code = data.code
12
+ @error_msg = data.message
13
+ end
14
+
15
+ # A full description of the error.
16
+ # @return [String]
17
+ def message
18
+ "Telphin returned an error #{@error_code}: '#{@error_msg}'"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ module TelphinApi
2
+ # Faraday middleware for logging requests and responses.
3
+ #
4
+ # It's behaviour depends on the logging options in the configuration.
5
+ class Logger < Faraday::Response::Middleware
6
+ # Creates a middleware instance.
7
+ # The logger is set from `:logger` configuration option.
8
+ def initialize(app)
9
+ super(app)
10
+ @logger = TelphinApi.logger
11
+ end
12
+
13
+ # Logs the request if needed.
14
+ # @param [Hash] env Request data.
15
+ def call(env)
16
+ if TelphinApi.log_requests?
17
+ @logger.debug "#{env[:method].to_s.upcase} #{env[:url].to_s}"
18
+ @logger.debug "body: #{env[:body].inspect}" unless env[:method] == :get
19
+ end
20
+
21
+ super
22
+ end
23
+
24
+ # Logs the response (successful or not) if needed.
25
+ # @param [Hash] env Response data.
26
+ def on_complete(env)
27
+ if env[:body].error?
28
+ @logger.warn env[:raw_body] if TelphinApi.log_errors?
29
+ else
30
+ @logger.debug env[:raw_body] if TelphinApi.log_responses?
31
+ end
32
+ end
33
+ end
34
+
35
+ Faraday::Response.register_middleware telphin_logger: Logger
36
+ end
@@ -0,0 +1,39 @@
1
+ module TelphinApi
2
+ # An API method. It is responsible for generating it's full name and determining it's type.
3
+ class Method
4
+ include Resolvable
5
+
6
+ # A pattern for names of methods with a boolean result.
7
+ PREDICATE_NAMES = /^is.*\?$/
8
+
9
+ # Calling the API method.
10
+ # It delegates the network request to `API.call` and result processing to `Result.process`.
11
+ # @param [Hash] args Arguments for the API method.
12
+ def call(args = {}, &block)
13
+ response = API.call(full_method, args, token)
14
+ Result.process(response, type, block)
15
+ end
16
+
17
+ private
18
+ def full_method
19
+ [@previous_resolver.name, @name].compact.map { |part| camelize(part).gsub(/[^A-Za-z.]/, '') }
20
+ end
21
+
22
+ def type
23
+ @name =~ PREDICATE_NAMES ? :boolean : :anything
24
+ end
25
+
26
+ # camelize('get_profiles')
27
+ # => 'getProfiles'
28
+ def camelize(name)
29
+ words = name.split('_')
30
+ first_word = words.shift
31
+
32
+ words.each do |word|
33
+ word.sub!(/^[a-z]/, &:upcase)
34
+ end
35
+
36
+ words.unshift(first_word).join
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ module TelphinApi
2
+ # An API method namespace (such as `extensions` or `phone_calls`).
3
+ #
4
+ # It includes `Resolvable` and `Resolver` and calls API methods via `Resolver#call_method`.
5
+ # It also holds the list of all known namespaces.
6
+ class Namespace
7
+ include Resolvable
8
+ include Resolver
9
+
10
+ # Creates and calls the `TelphinApi::Method` using `TelphinApi::Resolver#call_method`.
11
+ def method_missing(*args, &block)
12
+ call_method(args, &block)
13
+ end
14
+
15
+ class << self
16
+ # An array of all method namespaces.
17
+ #
18
+ # Lazily loads the list from `namespaces.yml` and caches it.
19
+ # @return [Array] An array of strings
20
+ def names
21
+ if @names.nil?
22
+ filename = File.expand_path('../namespaces.yml', __FILE__)
23
+ @names = YAML.load_file(filename)
24
+ end
25
+
26
+ @names
27
+ end
28
+
29
+ # Does a given namespace exist?
30
+ # @param [String, Symbol] name
31
+ def exists?(name)
32
+ names.include?(name.to_s)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ - extensions
2
+ - phone_calls
3
+ - cdr
4
+ - faxes
@@ -0,0 +1,20 @@
1
+ module TelphinApi
2
+ # A mixin for classes that will be resolved via `#method_missing`.
3
+ module Resolvable
4
+ attr_reader :name
5
+
6
+ # Creates a resolvable object keeping it's name and the object that resolved it.
7
+ # @param [String] name The name of this resolvable.
8
+ # @option options [Hashie::Mash] :resolver A mash holding information about the previous resolver.
9
+ def initialize(name, options = {})
10
+ @name = name.to_s
11
+ @previous_resolver = options.delete(:resolver)
12
+ end
13
+
14
+ # Returns the token from the previous resolver.
15
+ # @return [String] A token.
16
+ def token
17
+ @previous_resolver.token
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ module TelphinApi
2
+ # A mixin for classes that will resolve other classes' objects via `#method_missing`.
3
+ module Resolver
4
+ # A `Hashie::Mash` structure holding the name and token of current instance.
5
+ # @return [Hashie::Mash]
6
+ def resolver
7
+ @resolver ||= Hashie::Mash.new(name: @name, token: token)
8
+ end
9
+
10
+ private
11
+ def create_namespace(name)
12
+ Namespace.new(name, resolver: resolver)
13
+ end
14
+
15
+ def create_method(name)
16
+ Method.new(name, resolver: resolver)
17
+ end
18
+
19
+ def call_method(args, &block)
20
+ create_method(args.shift).call(args.first || {}, &block)
21
+ end
22
+
23
+ class << self
24
+ # When this module is included, it undefines the `:send` instance method in the `base_class`
25
+ # so it can be resolved via `method_missing`.
26
+ def included(base_class)
27
+ base_class.class_eval do
28
+ undef_method :send
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ module TelphinApi
2
+ # A module that handles method result processing.
3
+ #
4
+ # It implements method blocks support, result typecasting and raises `TelphinApi::Error` in case of an error response.
5
+ module Result
6
+ class << self
7
+ # The main method result processing.
8
+ # @param [Hashie::Mash] response The server response in mash format.
9
+ # @param [Symbol] type The expected result type (`:boolean` or `:anything`).
10
+ # @param [Proc] block A block passed to the API method.
11
+ # @return [Array, Hashie::Mash] The processed result.
12
+ # @raise [TelphinApi::Error] raised when Telphin returns an error response.
13
+ def process(response, type, block)
14
+ result = extract_result(response)
15
+
16
+ if result.respond_to?(:each)
17
+ # enumerable result receives :map with a block when called with a block
18
+ # or is returned untouched otherwise
19
+ block.nil? ? result : result.map(&block)
20
+ else
21
+ # non-enumerable result is typecasted
22
+ # (and yielded if block_given?)
23
+ result = typecast(result, type)
24
+ block.nil? ? result : block.call(result)
25
+ end
26
+ end
27
+
28
+ private
29
+ def extract_result(response)
30
+ if response.error?
31
+ raise TelphinApi::Error.new(response.error)
32
+ else
33
+ response
34
+ end
35
+ end
36
+
37
+ def typecast(parameter, type)
38
+ case type
39
+ when :boolean
40
+ # '1' becomes true, '0' becomes false
41
+ !parameter.to_i.zero?
42
+ else
43
+ parameter
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ module TelphinApi
2
+ # An utility module able to flatten arguments (join arrays into comma-separated strings).
3
+ module Utils
4
+ class << self
5
+ # A multiple version of `#flatten_argument`. It transforms a hash flattening each value and keeping the keys untouched.
6
+ # @param [Hash] arguments The arguments to flatten.
7
+ # @return [Hash] Flattened arguments.
8
+ def flatten_arguments(arguments)
9
+ arguments.inject({}) do |flat_args, (arg_name, arg_value)|
10
+ flat_args[arg_name] = flatten_argument(arg_value)
11
+ flat_args
12
+ end
13
+ end
14
+
15
+ # If an argument is an array, it will be joined with a comma; otherwise it'll be returned untouched.
16
+ # @param [Object] argument The argument to flatten.
17
+ def flatten_argument(argument)
18
+ if argument.respond_to?(:join)
19
+ # if argument is an array, we join it with a comma
20
+ argument.join(',')
21
+ else
22
+ # otherwise leave it untouched
23
+ argument
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end