printreleaf 1.0.1

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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'irb'
4
+ require 'irb/completion'
5
+
6
+ require "bundler/setup"
7
+ require "printreleaf"
8
+
9
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
10
+ IRB.conf[:AUTO_INDENT] = true
11
+ IRB.conf[:SAVE_HISTORY] = 100
12
+
13
+ IRB.start
14
+
@@ -0,0 +1,37 @@
1
+ # stdlib
2
+ require "date"
3
+ require "forwardable"
4
+ require "json"
5
+
6
+ # dependencies
7
+ require "hashie"
8
+ require "restclient"
9
+
10
+ # libs
11
+ require "printreleaf/version"
12
+ require "printreleaf/error"
13
+ require "printreleaf/util"
14
+ require "printreleaf/transforms"
15
+ require "printreleaf/api"
16
+ require "printreleaf/resource"
17
+ require "printreleaf/relation"
18
+ require "printreleaf/actions"
19
+
20
+ require "printreleaf/forestry/project"
21
+ require "printreleaf/paper/type"
22
+
23
+ require "printreleaf/account"
24
+ require "printreleaf/certificate"
25
+ require "printreleaf/deposit"
26
+ require "printreleaf/invitation"
27
+ require "printreleaf/server"
28
+ require "printreleaf/source"
29
+ require "printreleaf/transaction_item"
30
+ require "printreleaf/transaction"
31
+ require "printreleaf/user"
32
+ require "printreleaf/volume_period"
33
+
34
+ module PrintReleaf
35
+ extend API
36
+ end
37
+
@@ -0,0 +1,100 @@
1
+ module PrintReleaf
2
+ class Account < Resource
3
+ path "/accounts"
4
+
5
+ action :find
6
+ action :list
7
+ action :create
8
+ action :update
9
+ action :activate
10
+ action :deactivate
11
+ action :delete
12
+
13
+ property :id
14
+ property :name
15
+ property :role
16
+ property :parent_id
17
+ property :status
18
+ property :created_at, transform_with: Transforms::Date
19
+ property :activated_at, transform_with: Transforms::Date
20
+ property :deactivated_at, transform_with: Transforms::Date
21
+ property :accounts_count, transform_with: Transforms::Integer
22
+ property :users_count, transform_with: Transforms::Integer
23
+ property :mtd_pages, transform_with: Transforms::Integer
24
+ property :qtd_pages, transform_with: Transforms::Integer
25
+ property :ytd_pages, transform_with: Transforms::Integer
26
+ property :lifetime_pages, transform_with: Transforms::Integer
27
+ property :mtd_trees, transform_with: Transforms::Float
28
+ property :qtd_trees, transform_with: Transforms::Float
29
+ property :ytd_trees, transform_with: Transforms::Float
30
+ property :lifetime_trees, transform_with: Transforms::Float
31
+
32
+ def self.mine
33
+ response = PrintReleaf.get("/account")
34
+ self.new(response)
35
+ end
36
+
37
+ # Account URI is always root, even when it has an owner.
38
+ # /accounts/456
39
+ # Instead of:
40
+ # /accounts/123/accounts/456
41
+ def uri
42
+ Util.join_uri(self.class.uri, self.id)
43
+ end
44
+
45
+ def active?
46
+ status == "active"
47
+ end
48
+
49
+ def inactive?
50
+ status == "inactive"
51
+ end
52
+
53
+ def parent
54
+ return nil if parent_id.nil?
55
+ @parent ||= Account.find(parent_id)
56
+ end
57
+
58
+ # Alias
59
+ def children
60
+ accounts
61
+ end
62
+
63
+ def accounts
64
+ @accounts ||= Relation.new(self, Account)
65
+ end
66
+
67
+ def certificates
68
+ @certificates ||= Relation.new(self, Certificate)
69
+ end
70
+
71
+ def deposits
72
+ @deposits ||= Relation.new(self, Deposit)
73
+ end
74
+
75
+ def invitations
76
+ @invitations ||= Relation.new(self, Invitation)
77
+ end
78
+
79
+ def servers
80
+ @servers ||= Relation.new(self, Server)
81
+ end
82
+
83
+ def sources
84
+ @sources ||= Relation.new(self, Source)
85
+ end
86
+
87
+ def transactions
88
+ @transactions ||= Relation.new(self, Transaction)
89
+ end
90
+
91
+ def users
92
+ @users ||= Relation.new(self, User)
93
+ end
94
+
95
+ def volume
96
+ @volume ||= Relation.new(self, VolumePeriod)
97
+ end
98
+ end
99
+ end
100
+
@@ -0,0 +1,123 @@
1
+ module PrintReleaf
2
+ module Actions
3
+ module Find
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ base.include(InstanceMethods)
7
+ end
8
+
9
+ def self.extended(base)
10
+ base.extend(ClassMethods)
11
+ base.extend(InstanceMethods)
12
+ end
13
+
14
+ module ClassMethods
15
+ def find(id)
16
+ uri = Util.join_uri(self.uri, id)
17
+ response = PrintReleaf.get(uri)
18
+ self.new(response)
19
+ end
20
+ end
21
+
22
+ module InstanceMethods
23
+ def reload
24
+ response = PrintReleaf.get(self.uri)
25
+ self.reset(response)
26
+ end
27
+ end
28
+ end
29
+
30
+ module List
31
+ def self.included(base)
32
+ base.extend(ClassMethods)
33
+ base.include(InstanceMethods)
34
+ end
35
+
36
+ def self.extended(base)
37
+ base.extend(InstanceMethods)
38
+ end
39
+
40
+ module ClassMethods
41
+ def list(params={})
42
+ PrintReleaf.get(self.uri, params).map do |response|
43
+ self.new(response)
44
+ end
45
+ end
46
+
47
+ def first
48
+ list.first
49
+ end
50
+
51
+ def last
52
+ list.last
53
+ end
54
+
55
+ def count
56
+ list.count
57
+ end
58
+
59
+ def length
60
+ list.length
61
+ end
62
+ end
63
+
64
+ module InstanceMethods
65
+ def list(params={})
66
+ PrintReleaf.get(self.uri, params).map do |response|
67
+ self.new(response)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ module Create
74
+ def self.included(base)
75
+ base.extend(self)
76
+ end
77
+
78
+ def create(params)
79
+ response = PrintReleaf.post(self.uri, params)
80
+ self.new(response)
81
+ end
82
+ end
83
+
84
+ module Update
85
+ def save
86
+ if self.id.nil?
87
+ response = PrintReleaf.post(self.uri, self.to_hash)
88
+ else
89
+ response = PrintReleaf.patch(self.uri, changes)
90
+ end
91
+ reset(response)
92
+ return true
93
+ end
94
+ end
95
+
96
+ module Delete
97
+ def delete
98
+ response = PrintReleaf.delete(self.uri)
99
+ reset(response)
100
+ return true
101
+ end
102
+ end
103
+
104
+ module Activate
105
+ def activate
106
+ uri = Util.join_uri(self.uri, "activate")
107
+ response = PrintReleaf.post(uri)
108
+ reset(response)
109
+ return true
110
+ end
111
+ end
112
+
113
+ module Deactivate
114
+ def deactivate
115
+ uri = Util.join_uri(self.uri, "deactivate")
116
+ response = PrintReleaf.post(uri)
117
+ reset(response)
118
+ return true
119
+ end
120
+ end
121
+ end
122
+ end
123
+
@@ -0,0 +1,162 @@
1
+ module PrintReleaf
2
+ module API
3
+ extend self
4
+
5
+ ENDPOINT = "api.printreleaf.com/v1/"
6
+ PROTOCOL = "https"
7
+ MAX_RETRY_COUNT = 2
8
+ RETRY_DELAY_BASE = 1.5 # Base for exponential delay
9
+
10
+ NETWORK_EXCEPTIONS = [
11
+ SocketError,
12
+ Errno::ECONNREFUSED,
13
+ Errno::ECONNRESET,
14
+ Errno::ETIMEDOUT,
15
+ RestClient::RequestTimeout
16
+ ]
17
+
18
+ API_EXCEPTIONS = {
19
+ 400 => BadRequest,
20
+ 401 => Unauthorized,
21
+ 403 => Forbidden,
22
+ 404 => NotFound,
23
+ 429 => RateLimitExceeded,
24
+ 500 => ServerError
25
+ }
26
+
27
+ attr_writer :api_key
28
+ attr_writer :endpoint
29
+ attr_writer :protocol
30
+ attr_accessor :logger
31
+
32
+ def api_key
33
+ if @api_key.nil? or @api_key.strip.to_s.empty?
34
+ raise Error, "Missing API Key."
35
+ else
36
+ return @api_key
37
+ end
38
+ end
39
+
40
+ def endpoint
41
+ @endpoint || ENDPOINT
42
+ end
43
+
44
+ def protocol
45
+ @protocol || PROTOCOL
46
+ end
47
+
48
+ def get(uri="/", params={})
49
+ request :get, uri, params
50
+ end
51
+
52
+ def post(uri, data={})
53
+ request :post, uri, data
54
+ end
55
+
56
+ def patch(uri, data={})
57
+ request :patch, uri, data
58
+ end
59
+
60
+ def delete(uri)
61
+ request :delete, uri
62
+ end
63
+
64
+ def request(verb, uri, params={})
65
+ perform_request do
66
+ uri = Util.join_uri(endpoint, uri)
67
+ url = "#{protocol}://#{api_key}:@#{uri}"
68
+
69
+ unless logger.nil?
70
+ logger.info "[PrintReleaf] #{verb.upcase} #{uri}"
71
+ end
72
+
73
+ response = case verb
74
+ when :get; RestClient.get url, params: params, accept: :json
75
+ when :post; RestClient.post url, params.to_json, accept: :json, content_type: :json
76
+ when :patch; RestClient.patch url, params.to_json, accept: :json, content_type: :json
77
+ when :delete; RestClient.delete url
78
+ end
79
+
80
+ JSON.parse(response.body)
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def perform_request
87
+ retry_count = 0
88
+ begin
89
+ yield
90
+ rescue => e
91
+ if should_retry?(e, retry_count)
92
+ retry_count += 1
93
+ sleep retry_delay(retry_count)
94
+ retry
95
+ else
96
+ handle_error(e, retry_count)
97
+ end
98
+ end
99
+ end
100
+
101
+ def handle_error(e, retry_count)
102
+ case e
103
+ when RestClient::ExceptionWithResponse
104
+ if e.response
105
+ handle_api_error(e, retry_count)
106
+ else
107
+ handle_restclient_error(e, retry_count)
108
+ end
109
+ when JSON::ParserError
110
+ handle_json_error(e, retry_count)
111
+ when *NETWORK_EXCEPTIONS
112
+ handle_network_error(e, retry_count)
113
+ else
114
+ raise
115
+ end
116
+ end
117
+
118
+ def handle_api_error(e, retry_count=0)
119
+ # We likely got an http status code outside the 200-399 range.
120
+ # If this is a GET or DELETE request, it is likely the resource is not owned by the client.
121
+ # If this is a POST, PUT, or PATCH, the data might be invalid.
122
+ code = e.response.code
123
+ message = e.response ? JSON.parse(e.response.body)["error"] : "Something went wrong. Please try again."
124
+ message += " (code=#{code})"
125
+ message += " Request was retried #{retry_count} times." if retry_count > 0
126
+ exception = API_EXCEPTIONS[e.response.code] || Error
127
+ raise exception, message
128
+ end
129
+
130
+ def handle_json_error(e, retry_count=0)
131
+ # We received the data fine, but we're unable to parse it.
132
+ # Re-raise a generic error.
133
+ message = "Unable to parse response. Please try again."
134
+ message += " Request was retried #{retry_count} times." if retry_count > 0
135
+ message += " (#{e.class.name})"
136
+ raise ResponseError, message
137
+ end
138
+
139
+ def handle_network_error(e, retry_count=0)
140
+ message = "Unexpected error communicating when trying to connect to PrintReleaf."
141
+ message += " Request was retried #{retry_count} times." if retry_count > 0
142
+ message += " (#{e.class.name})"
143
+ raise NetworkError, message
144
+ end
145
+
146
+ def handle_restclient_error(e, retry_count=0)
147
+ message = "Something went wrong with the request. Please try again."
148
+ message += " Request was retried #{retry_count} times." if retry_count > 0
149
+ message += " (#{e.class.name})"
150
+ raise RequestError, message
151
+ end
152
+
153
+ def should_retry?(e, retry_count=0)
154
+ NETWORK_EXCEPTIONS.include?(e.class) && retry_count < MAX_RETRY_COUNT
155
+ end
156
+
157
+ def retry_delay(retry_count=0)
158
+ RETRY_DELAY_BASE ** retry_count
159
+ end
160
+ end
161
+ end
162
+