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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.travis.yml +19 -0
- data/.yardopts +3 -0
- data/Gemfile +4 -0
- data/Guardfile +14 -0
- data/MIT-LICENSE +20 -0
- data/README.md +197 -0
- data/Rakefile +13 -0
- data/bin/rspec +15 -0
- data/example/Gemfile +7 -0
- data/example/app.rb +23 -0
- data/lib/generators/telphin_api/install/USAGE +2 -0
- data/lib/generators/telphin_api/install/install_generator.rb +10 -0
- data/lib/generators/telphin_api/install/templates/initializer.rb +25 -0
- data/lib/telphin_api.rb +39 -0
- data/lib/telphin_api/api.rb +59 -0
- data/lib/telphin_api/authorization.rb +38 -0
- data/lib/telphin_api/client.rb +75 -0
- data/lib/telphin_api/configuration.rb +75 -0
- data/lib/telphin_api/error.rb +21 -0
- data/lib/telphin_api/logger.rb +36 -0
- data/lib/telphin_api/method.rb +39 -0
- data/lib/telphin_api/namespace.rb +36 -0
- data/lib/telphin_api/namespaces.yml +4 -0
- data/lib/telphin_api/resolvable.rb +20 -0
- data/lib/telphin_api/resolver.rb +33 -0
- data/lib/telphin_api/result.rb +48 -0
- data/lib/telphin_api/utils.rb +28 -0
- data/lib/telphin_api/version.rb +4 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/telphin_spec.rb +7 -0
- data/telphin_api.gemspec +45 -0
- metadata +332 -0
@@ -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,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
|