fabricio 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|