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