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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +563 -0
- data/Rakefile +6 -0
- data/bin/printreleaf-console +14 -0
- data/lib/printreleaf.rb +37 -0
- data/lib/printreleaf/account.rb +100 -0
- data/lib/printreleaf/actions.rb +123 -0
- data/lib/printreleaf/api.rb +162 -0
- data/lib/printreleaf/certificate.rb +27 -0
- data/lib/printreleaf/deposit.rb +35 -0
- data/lib/printreleaf/error.rb +35 -0
- data/lib/printreleaf/forestry/project.rb +30 -0
- data/lib/printreleaf/invitation.rb +20 -0
- data/lib/printreleaf/paper/type.rb +23 -0
- data/lib/printreleaf/relation.rb +70 -0
- data/lib/printreleaf/resource.rb +93 -0
- data/lib/printreleaf/server.rb +24 -0
- data/lib/printreleaf/source.rb +37 -0
- data/lib/printreleaf/transaction.rb +32 -0
- data/lib/printreleaf/transaction_item.rb +15 -0
- data/lib/printreleaf/transforms.rb +8 -0
- data/lib/printreleaf/user.rb +20 -0
- data/lib/printreleaf/util.rb +10 -0
- data/lib/printreleaf/version.rb +3 -0
- data/lib/printreleaf/volume_period.rb +17 -0
- data/printreleaf.gemspec +27 -0
- metadata +151 -0
data/Rakefile
ADDED
data/lib/printreleaf.rb
ADDED
@@ -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
|
+
|