fabricio 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +12 -0
  3. data/.idea/runConfigurations/IRB_console__fabricio.xml +23 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1156 -0
  6. data/.travis.yml +10 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +149 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/docs/api_reference.md +611 -0
  15. data/docs/swagger-api.json +553 -0
  16. data/fabricio.gemspec +29 -0
  17. data/lib/fabricio.rb +2 -0
  18. data/lib/fabricio/authorization/abstract_session_storage.rb +26 -0
  19. data/lib/fabricio/authorization/authorization_client.rb +122 -0
  20. data/lib/fabricio/authorization/memory_session_storage.rb +35 -0
  21. data/lib/fabricio/authorization/session.rb +21 -0
  22. data/lib/fabricio/client/client.rb +92 -0
  23. data/lib/fabricio/models/abstract_model.rb +17 -0
  24. data/lib/fabricio/models/app.rb +24 -0
  25. data/lib/fabricio/models/build.rb +23 -0
  26. data/lib/fabricio/models/organization.rb +22 -0
  27. data/lib/fabricio/models/point.rb +17 -0
  28. data/lib/fabricio/networking/app_request_model_factory.rb +229 -0
  29. data/lib/fabricio/networking/build_request_model_factory.rb +103 -0
  30. data/lib/fabricio/networking/network_client.rb +101 -0
  31. data/lib/fabricio/networking/organization_request_model_factory.rb +27 -0
  32. data/lib/fabricio/networking/request_model.rb +39 -0
  33. data/lib/fabricio/services/app_service.rb +146 -0
  34. data/lib/fabricio/services/build_service.rb +59 -0
  35. data/lib/fabricio/services/organization_service.rb +33 -0
  36. data/lib/fabricio/version.rb +3 -0
  37. 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,2 @@
1
+ require 'fabricio/version'
2
+ require 'fabricio/client/client'
@@ -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