fabricio 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/.codeclimate.yml +12 -0
- data/.idea/runConfigurations/IRB_console__fabricio.xml +23 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +10 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +149 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/api_reference.md +611 -0
- data/docs/swagger-api.json +553 -0
- data/fabricio.gemspec +29 -0
- data/lib/fabricio.rb +2 -0
- data/lib/fabricio/authorization/abstract_session_storage.rb +26 -0
- data/lib/fabricio/authorization/authorization_client.rb +122 -0
- data/lib/fabricio/authorization/memory_session_storage.rb +35 -0
- data/lib/fabricio/authorization/session.rb +21 -0
- data/lib/fabricio/client/client.rb +92 -0
- data/lib/fabricio/models/abstract_model.rb +17 -0
- data/lib/fabricio/models/app.rb +24 -0
- data/lib/fabricio/models/build.rb +23 -0
- data/lib/fabricio/models/organization.rb +22 -0
- data/lib/fabricio/models/point.rb +17 -0
- data/lib/fabricio/networking/app_request_model_factory.rb +229 -0
- data/lib/fabricio/networking/build_request_model_factory.rb +103 -0
- data/lib/fabricio/networking/network_client.rb +101 -0
- data/lib/fabricio/networking/organization_request_model_factory.rb +27 -0
- data/lib/fabricio/networking/request_model.rb +39 -0
- data/lib/fabricio/services/app_service.rb +146 -0
- data/lib/fabricio/services/build_service.rb +59 -0
- data/lib/fabricio/services/organization_service.rb +33 -0
- data/lib/fabricio/version.rb +3 -0
- metadata +163 -0
data/fabricio.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fabricio/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "fabricio"
|
8
|
+
spec.version = Fabricio::VERSION
|
9
|
+
spec.authors = ["Egor Tolstoy"]
|
10
|
+
spec.email = ["e.tolstoy@rambler-co.ru"]
|
11
|
+
|
12
|
+
spec.summary = "A simple gem that fetches mobile application statistics from Fabric.io API."
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency 'faraday'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
26
|
+
spec.add_development_dependency 'webmock'
|
27
|
+
spec.add_development_dependency 'simplecov'
|
28
|
+
spec.add_development_dependency 'codeclimate-test-reporter', '~> 1.0.0'
|
29
|
+
end
|
data/lib/fabricio.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'fabricio/authorization/session'
|
2
|
+
|
3
|
+
module Fabricio
|
4
|
+
module Authorization
|
5
|
+
# A class providing an interface for implementing Fabric session storage. Subclass it to provide your own behaviour (e.g. storing session data in database)
|
6
|
+
class AbstractSessionStorage
|
7
|
+
|
8
|
+
# Override it with your own behavior of obtaining a [Fabricio::Authorization::Session] object
|
9
|
+
#
|
10
|
+
# @return [Fabricio::Authorization::Session]
|
11
|
+
def obtain_session
|
12
|
+
raise NotImplementedError, "Implement this method in a child class"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Override it with your own behavior of storing a [Fabricio::Authorization::Session] object
|
16
|
+
def store_session(_)
|
17
|
+
raise NotImplementedError, "Implement this method in a child class"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Override it with your own behavior of deleting stored [Fabricio::Authorization::Session] object
|
21
|
+
def reset
|
22
|
+
raise NotImplementedError, "Implement this method in a child class"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
require 'fabricio/authorization/session'
|
4
|
+
require 'fabricio/services/organization_service'
|
5
|
+
|
6
|
+
AUTH_API_URL = 'https://fabric.io/oauth/token'
|
7
|
+
ORGANIZATION_API_URL = 'https://fabric.io/api/v2/organizations'
|
8
|
+
|
9
|
+
module Fabricio
|
10
|
+
module Authorization
|
11
|
+
# A class used for user authorization.
|
12
|
+
class AuthorizationClient
|
13
|
+
|
14
|
+
# Returns a session object for making API requests.
|
15
|
+
#
|
16
|
+
# @param username [String]
|
17
|
+
# @param password [String]
|
18
|
+
# @param client_id [String]
|
19
|
+
# @param client_secret [String]
|
20
|
+
# @return [Fabricio::Authorization::Session]
|
21
|
+
def auth(username, password, client_id, client_secret)
|
22
|
+
perform_authorization(username, password, client_id, client_secret)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Refreshes an expired session using refresh_token
|
26
|
+
#
|
27
|
+
# @param session [Fabricio::Authorization::Session] Expired session
|
28
|
+
# @return [Fabricio::Authorization::Session]
|
29
|
+
def refresh(session)
|
30
|
+
perform_refresh_token_request(session)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Initiates two network requests. One for obtaining session (access and refresh tokens).
|
36
|
+
# Another one for obtaining organization identifier.
|
37
|
+
#
|
38
|
+
# @param username [String]
|
39
|
+
# @param password [String]
|
40
|
+
# @param client_id [String]
|
41
|
+
# @param client_secret [String]
|
42
|
+
# @raise [StandardError] Raises error if server sends incorrect response
|
43
|
+
# @return [Fabricio::Authorization::Session]
|
44
|
+
def perform_authorization(username, password, client_id, client_secret)
|
45
|
+
auth_data = obtain_auth_data(username, password, client_id, client_secret)
|
46
|
+
if auth_data['access_token'] == nil
|
47
|
+
raise StandardError.new("Incorrect authorization response: #{auth_data}")
|
48
|
+
end
|
49
|
+
organization_id = obtain_organization_id(auth_data)
|
50
|
+
Session.new(auth_data, organization_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Initiates a session refresh network request
|
54
|
+
#
|
55
|
+
# @param session [Fabricio::Authorization::Session] Expired session
|
56
|
+
# @raise [StandardError] Raises error if server sends incorrect response
|
57
|
+
# @return [Fabricio::Authorization::Session]
|
58
|
+
def perform_refresh_token_request(session)
|
59
|
+
conn = Faraday.new(:url => AUTH_API_URL) do |faraday|
|
60
|
+
faraday.adapter Faraday.default_adapter
|
61
|
+
end
|
62
|
+
|
63
|
+
response = conn.post do |req|
|
64
|
+
req.headers['Content-Type'] = 'application/json'
|
65
|
+
req.body = {
|
66
|
+
'grant_type' => 'refresh_token',
|
67
|
+
'refresh_token' => session.refresh_token
|
68
|
+
}.to_json
|
69
|
+
end
|
70
|
+
result = JSON.parse(response.body)
|
71
|
+
if result['access_token'] == nil
|
72
|
+
raise StandardError.new("Incorrect authorization response: #{auth_data}")
|
73
|
+
end
|
74
|
+
Session.new(result, session.organization_id)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Makes a request to OAuth API and obtains access and refresh tokens.
|
78
|
+
#
|
79
|
+
# @param username [String]
|
80
|
+
# @param password [String]
|
81
|
+
# @param client_id [String]
|
82
|
+
# @param client_secret [String]
|
83
|
+
# @return [Hash]
|
84
|
+
def obtain_auth_data(username, password, client_id, client_secret)
|
85
|
+
conn = Faraday.new(:url => AUTH_API_URL) do |faraday|
|
86
|
+
faraday.adapter Faraday.default_adapter
|
87
|
+
end
|
88
|
+
|
89
|
+
response = conn.post do |req|
|
90
|
+
req.headers['Content-Type'] = 'application/json'
|
91
|
+
req.body = {
|
92
|
+
'grant_type' => 'password',
|
93
|
+
'scope' => 'organizations apps issues features account twitter_client_apps beta software answers',
|
94
|
+
'username' => username,
|
95
|
+
'password' => password,
|
96
|
+
'client_id' => client_id,
|
97
|
+
'client_secret' => client_secret
|
98
|
+
}.to_json
|
99
|
+
end
|
100
|
+
JSON.parse(response.body)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Makes a request to fetch current organization identifier.
|
104
|
+
# This identifier is used in most other API requests, so we store it in the session.
|
105
|
+
#
|
106
|
+
# @param auth_data [Hash] A set of authorization tokens
|
107
|
+
# @option options [String] :access_token OAuth access token
|
108
|
+
# @option options [String] :refresh_token OAuth refresh token
|
109
|
+
# @return [String]
|
110
|
+
def obtain_organization_id(auth_data)
|
111
|
+
conn = Faraday.new(:url => ORGANIZATION_API_URL) do |faraday|
|
112
|
+
faraday.adapter Faraday.default_adapter
|
113
|
+
end
|
114
|
+
|
115
|
+
response = conn.get do |req|
|
116
|
+
req.headers['Authorization'] = "Bearer #{auth_data['access_token']}"
|
117
|
+
end
|
118
|
+
JSON.parse(response.body).first['id']
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'fabricio/authorization/abstract_session_storage'
|
2
|
+
|
3
|
+
module Fabricio
|
4
|
+
module Authorization
|
5
|
+
# The only one built-in session storage. Stores current session in memory.
|
6
|
+
class MemorySessionStorage < AbstractSessionStorage
|
7
|
+
|
8
|
+
# Initializes a new MemorySessionStorage object
|
9
|
+
#
|
10
|
+
# @return [Fabricio::Authorization::MemorySessionStorage]
|
11
|
+
def initialize
|
12
|
+
@session = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns session stored in a variable
|
16
|
+
#
|
17
|
+
# @return [Fabricio::Authorization::Session]
|
18
|
+
def obtain_session
|
19
|
+
@session
|
20
|
+
end
|
21
|
+
|
22
|
+
# Stores session in a variable
|
23
|
+
#
|
24
|
+
# @param session [Fabricio::Authorization::MemorySessionStorage]
|
25
|
+
def store_session(session)
|
26
|
+
@session = session
|
27
|
+
end
|
28
|
+
|
29
|
+
# Resets current state and deletes saved session
|
30
|
+
def reset
|
31
|
+
@session = nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Fabricio
|
2
|
+
module Authorization
|
3
|
+
# This class is a data structure that holds tokens and identifiers necessary for making API requests.
|
4
|
+
class Session
|
5
|
+
attr_reader :access_token, :refresh_token, :organization_id
|
6
|
+
|
7
|
+
# Initializes a new Session object
|
8
|
+
#
|
9
|
+
# @param attributes [Hash] Hash containing access and refresh tokens
|
10
|
+
# @option options [String] :access_token OAuth access token
|
11
|
+
# @option options [String] :refresh_token OAuth refresh token
|
12
|
+
# @param organization_id [String]
|
13
|
+
# @return [Fabricio::Authorization::Session]
|
14
|
+
def initialize(attributes = {}, organization_id = '')
|
15
|
+
@access_token = attributes['access_token']
|
16
|
+
@refresh_token = attributes['refresh_token']
|
17
|
+
@organization_id = organization_id
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'fabricio/models/organization'
|
2
|
+
require 'fabricio/services/organization_service'
|
3
|
+
require 'fabricio/services/app_service'
|
4
|
+
require 'fabricio/services/build_service'
|
5
|
+
require 'fabricio/authorization/authorization_client'
|
6
|
+
require 'fabricio/authorization/session'
|
7
|
+
require 'fabricio/authorization/memory_session_storage'
|
8
|
+
require 'fabricio/networking/network_client'
|
9
|
+
|
10
|
+
module Fabricio
|
11
|
+
# The main object of the gem. It's used to initiate all data requests.
|
12
|
+
class Client
|
13
|
+
# Default values for initialization parameters
|
14
|
+
# clientId and clientSecret are taken from Android application.
|
15
|
+
DEFAULT_CLIENT_ID = '2c18f8a77609ee6bbac9e53f3768fedc45fb96be0dbcb41defa706dc57d9c931'
|
16
|
+
DEFAULT_CLIENT_SECRET = '092ed1cdde336647b13d44178932cba10911577faf0eda894896188a7d900cc9'
|
17
|
+
DEFAULT_USERNAME = nil
|
18
|
+
DEFAULT_PASSWORD = nil
|
19
|
+
# In-memory session storage is used by default
|
20
|
+
DEFAULT_SESSION_STORAGE = Fabricio::Authorization::MemorySessionStorage.new
|
21
|
+
|
22
|
+
attr_accessor :client_id, :client_secret, :username, :password, :session_storage;
|
23
|
+
|
24
|
+
# Initializes a new Client object. You can use a block to fill all the options:
|
25
|
+
# client = Fabricio::Client.new do |config|
|
26
|
+
# config.username = 'email@rambler.ru'
|
27
|
+
# config.password = 'pa$$word'
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# After initializing you can query this client to get data:
|
31
|
+
# client.app.all
|
32
|
+
# client.app.active_now('app_id')
|
33
|
+
#
|
34
|
+
# @param options [Hash] Hash containing customizable options
|
35
|
+
# @option options [String] :client_id Client identifier. You can take it from the 'Organization' section in Fabric.io settings.
|
36
|
+
# @option options [String] :client_secret Client secret key. You can take it from the 'Organization' section in Fabric.io settings.
|
37
|
+
# @option options [String] :username Your Fabric.io username
|
38
|
+
# @option options [String] :password Your Fabric.io password
|
39
|
+
# @option options [Fabricio::Authorization::AbstractSessionStorage] :session_storage Your custom AbstractSessionStorage subclass that provides its own logic of storing session data.
|
40
|
+
# @return [Fabricio::Client]
|
41
|
+
def initialize(options =
|
42
|
+
{
|
43
|
+
:client_id => DEFAULT_CLIENT_ID,
|
44
|
+
:client_secret => DEFAULT_CLIENT_SECRET,
|
45
|
+
:username => DEFAULT_USERNAME,
|
46
|
+
:password => DEFAULT_PASSWORD,
|
47
|
+
:session_storage => DEFAULT_SESSION_STORAGE
|
48
|
+
})
|
49
|
+
options.each do |key, value|
|
50
|
+
instance_variable_set("@#{key}", value)
|
51
|
+
end
|
52
|
+
yield(self) if block_given?
|
53
|
+
|
54
|
+
@auth_client = Fabricio::Authorization::AuthorizationClient.new
|
55
|
+
session = obtain_session
|
56
|
+
network_client = Fabricio::Networking::NetworkClient.new(@auth_client, @session_storage)
|
57
|
+
|
58
|
+
@organization_service ||= Fabricio::Service::OrganizationService.new(session, network_client)
|
59
|
+
@app_service ||= Fabricio::Service::AppService.new(session, network_client)
|
60
|
+
@build_service ||= Fabricio::Service::BuildService.new(session, network_client)
|
61
|
+
end
|
62
|
+
|
63
|
+
# We use `method_missing` approach instead of explicit methods.
|
64
|
+
# Generally, look at the initialized services and use the first part of their names as a method.
|
65
|
+
# app_service -> client.app
|
66
|
+
#
|
67
|
+
# # @raise [NoMethodError] Error raised when unsupported method is called.
|
68
|
+
def method_missing(*args)
|
69
|
+
service = instance_variable_get("@#{args.first}_service")
|
70
|
+
return service if service
|
71
|
+
raise NoMethodError.new("There's no method called #{args.first} here -- please try again.", args.first)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Obtains current session. If there is no cached session, it sends a request to OAuth API to get access and refresh tokens.
|
77
|
+
#
|
78
|
+
# @return [Fabricio::Authorization::Session]
|
79
|
+
def obtain_session
|
80
|
+
session = @session_storage.obtain_session
|
81
|
+
if !session
|
82
|
+
session = @auth_client.auth(@username,
|
83
|
+
@password,
|
84
|
+
@client_id,
|
85
|
+
@client_secret)
|
86
|
+
@session_storage.store_session(session)
|
87
|
+
end
|
88
|
+
|
89
|
+
session
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Fabricio
|
2
|
+
module Model
|
3
|
+
# Defines a base class for all data models
|
4
|
+
class AbstractModel
|
5
|
+
# Plain data from the server
|
6
|
+
attr_accessor :json
|
7
|
+
|
8
|
+
# We use `method_missing` approach here to allow a user query any field from the original data structure sent by server.
|
9
|
+
def method_missing(*args)
|
10
|
+
method_name = args.first
|
11
|
+
json_value = @json[method_name.to_s]
|
12
|
+
return json_value if json_value
|
13
|
+
raise NoMethodError.new("There's no method called #{args.first} here -- please try again.", args.first)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'fabricio/models/abstract_model'
|
2
|
+
|
3
|
+
module Fabricio
|
4
|
+
module Model
|
5
|
+
# This model represents an application
|
6
|
+
class App < AbstractModel
|
7
|
+
attr_reader :id, :name, :bundle_id, :created_at, :platform, :icon_url
|
8
|
+
|
9
|
+
# Returns an App model object
|
10
|
+
#
|
11
|
+
# @param attributes [Hash]
|
12
|
+
# @return [Fabricio::Model::App]
|
13
|
+
def initialize(attributes)
|
14
|
+
@id = attributes['id']
|
15
|
+
@name = attributes['name']
|
16
|
+
@bundle_id = attributes['bundle_identifier']
|
17
|
+
@created_at = attributes['created_at']
|
18
|
+
@platform = attributes['platform']
|
19
|
+
@icon_url = attributes['icon_url']
|
20
|
+
@json = attributes
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'fabricio/models/abstract_model'
|
2
|
+
|
3
|
+
module Fabricio
|
4
|
+
module Model
|
5
|
+
# This model represents an application build
|
6
|
+
class Build < AbstractModel
|
7
|
+
attr_reader :id, :version, :build_number, :release_notes, :distributed_at
|
8
|
+
|
9
|
+
# Returns a Build model object
|
10
|
+
#
|
11
|
+
# @param attributes [Hash]
|
12
|
+
# @return [Fabricio::Model::Build]
|
13
|
+
def initialize(attributes)
|
14
|
+
@id = attributes['id']
|
15
|
+
@version = attributes['build_version']['display_version']
|
16
|
+
@build_number = attributes['build_version']['build_version']
|
17
|
+
@release_notes = attributes['release_notes_summary']
|
18
|
+
@distributed_at = attributes['distributed_at']
|
19
|
+
@json = attributes
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'fabricio/models/abstract_model'
|
2
|
+
|
3
|
+
module Fabricio
|
4
|
+
module Model
|
5
|
+
# This model represents an organization
|
6
|
+
class Organization < AbstractModel
|
7
|
+
attr_reader :id, :alias, :name, :apps_counts
|
8
|
+
|
9
|
+
# Returns a Build model object
|
10
|
+
#
|
11
|
+
# @param attributes [Hash]
|
12
|
+
# @return [Fabricio::Model::Organization]
|
13
|
+
def initialize(attributes)
|
14
|
+
@id = attributes['id']
|
15
|
+
@alias = attributes['alias']
|
16
|
+
@name = attributes['name']
|
17
|
+
@apps_counts = attributes['apps_counts']
|
18
|
+
@json = attributes
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Fabricio
|
2
|
+
module Model
|
3
|
+
# This model represents a data point with two fields - timestamp and value
|
4
|
+
class Point
|
5
|
+
attr_reader :date, :value
|
6
|
+
|
7
|
+
# Returns a data point with two fields - timestamp and value
|
8
|
+
#
|
9
|
+
# @param attributes [Hash]
|
10
|
+
# @return [Fabricio::Model::Point]
|
11
|
+
def initialize(attributes)
|
12
|
+
@date = DateTime.strptime(attributes.first.to_s,'%s')
|
13
|
+
@value = attributes.last
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|