strongmind_canvas_api_client 0.1.0 → 0.1.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 +4 -4
- data/Gemfile.lock +7 -11
- data/lib/canvas/canvas_api.rb +342 -0
- data/lib/strongmind_canvas_api_client/version.rb +1 -1
- data/lib/strongmind_canvas_api_client.rb +1 -1
- metadata +6 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a031a2d0529c689391b4385842256b458a50d7d638be041da258672674ba1b0
|
4
|
+
data.tar.gz: b459407d9102314eb88f5968c696b9c6614797be12e5ff37047c3902a47c624f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 977be903e5a67dadb4afe3f9a16e0ac71092932e2e1cd721d8c679028a720026e9224b8f593b00bdb95648e6cbb5b028fadeaf1c33109b2c31d516ce064751f7
|
7
|
+
data.tar.gz: 0bc6fc112e6df9100aec16cf9d88a431c5b5d01b8cc0ac5c05c3d7679033ead7c8e7b3deb2b6bcd280cd8941257d89168da061529f0589cda8e07ad4cc9eb36f
|
data/Gemfile.lock
CHANGED
@@ -1,21 +1,18 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
strongmind_canvas_api_client (0.1.
|
5
|
-
|
4
|
+
strongmind_canvas_api_client (0.1.1)
|
5
|
+
faraday
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
ast (2.4.2)
|
11
|
-
canvas-api (1.1.1)
|
12
|
-
json
|
13
|
-
typhoeus
|
14
11
|
diff-lcs (1.5.0)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
faraday (2.7.10)
|
13
|
+
faraday-net_http (>= 2.0, < 3.1)
|
14
|
+
ruby2_keywords (>= 0.0.4)
|
15
|
+
faraday-net_http (3.0.2)
|
19
16
|
parallel (1.22.1)
|
20
17
|
parser (3.1.2.0)
|
21
18
|
ast (~> 2.4.1)
|
@@ -51,11 +48,10 @@ GEM
|
|
51
48
|
rubocop (>= 1.7.0, < 2.0)
|
52
49
|
rubocop-ast (>= 0.4.0)
|
53
50
|
ruby-progressbar (1.11.0)
|
51
|
+
ruby2_keywords (0.0.5)
|
54
52
|
standard (1.12.1)
|
55
53
|
rubocop (= 1.29.1)
|
56
54
|
rubocop-performance (= 1.13.3)
|
57
|
-
typhoeus (1.4.0)
|
58
|
-
ethon (>= 0.9.0)
|
59
55
|
unicode-display_width (2.1.0)
|
60
56
|
|
61
57
|
PLATFORMS
|
@@ -0,0 +1,342 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'faraday'
|
6
|
+
|
7
|
+
module Canvas
|
8
|
+
class API
|
9
|
+
def initialize(args = {})
|
10
|
+
@host = args[:host] && args[:host].to_s
|
11
|
+
@token = args[:token] && args[:token].to_s
|
12
|
+
@client_id = args[:client_id] && args[:client_id].to_s
|
13
|
+
@secret = args[:secret] && args[:secret].to_s
|
14
|
+
@insecure = !!args[:insecure]
|
15
|
+
raise "host required" unless @host
|
16
|
+
raise "invalid host, protocol required" unless @host.match(/^http/)
|
17
|
+
raise "invalid host" unless @host.match(/^https?:\/\/[^\/]+$/)
|
18
|
+
raise "token or client_id required" if !@token && !@client_id
|
19
|
+
raise "secret required for client_id configuration" if @client_id && !@secret
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :host
|
23
|
+
attr_accessor :token
|
24
|
+
attr_accessor :client_id
|
25
|
+
|
26
|
+
def masquerade_as(user_id)
|
27
|
+
@as_user_id = user_id && user_id.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop_masquerading
|
31
|
+
@as_user_id = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.encode_id(prefix, id)
|
35
|
+
return nil unless prefix && id
|
36
|
+
"hex:#{prefix}:" + id.to_s.unpack("H*")[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
def encode_id(prefix, id)
|
40
|
+
Canvas::API.encode_id(prefix, id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def oauth_url(callback_url, scopes = "")
|
44
|
+
raise "client_id required for oauth flow" unless @client_id
|
45
|
+
raise "secret required for oauth flow" unless @secret
|
46
|
+
raise "callback_url required" unless callback_url
|
47
|
+
raise "invalid callback_url" unless (URI.parse(callback_url) rescue nil)
|
48
|
+
scopes ||= ""
|
49
|
+
scopes = scopes.length > 0 ? "&scopes=#{CGI.escape(scopes)}" : ""
|
50
|
+
"#{@host}/login/oauth2/auth?client_id=#{@client_id}&response_type=code&redirect_uri=#{CGI.escape(callback_url)}#{scopes}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def login_url(callback_url)
|
54
|
+
oauth_url(callback_url, "/auth/userinfo")
|
55
|
+
end
|
56
|
+
|
57
|
+
def retrieve_access_token(code, callback_url)
|
58
|
+
raise "client_id required for oauth flow" unless @client_id
|
59
|
+
raise "secret required for oauth flow" unless @secret
|
60
|
+
raise "code required" unless code
|
61
|
+
raise "callback_url required" unless callback_url
|
62
|
+
raise "invalid callback_url" unless (URI.parse(callback_url) rescue nil)
|
63
|
+
@token = "ignore"
|
64
|
+
res = post("/login/oauth2/token", :client_id => @client_id, :redirect_uri => callback_url, :client_secret => @secret, :code => code)
|
65
|
+
if res['access_token']
|
66
|
+
@token = res['access_token']
|
67
|
+
end
|
68
|
+
res
|
69
|
+
end
|
70
|
+
|
71
|
+
def logout
|
72
|
+
!!delete("/login/oauth2/token")['logged_out']
|
73
|
+
end
|
74
|
+
|
75
|
+
def validate_call(endpoint)
|
76
|
+
raise "token required for api calls" unless @token
|
77
|
+
raise "missing host" unless @host
|
78
|
+
raise "missing endpoint" unless endpoint
|
79
|
+
raise "missing leading slash on endpoint" unless endpoint.match(/^\//)
|
80
|
+
raise "invalid endpoint" unless endpoint.match(/^\/api\/v\d+\//) unless @token == 'ignore'
|
81
|
+
raise "invalid endpoint" unless (URI.parse(endpoint) rescue nil)
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate_uri(endpoint, params = nil)
|
85
|
+
validate_call(endpoint)
|
86
|
+
unless @token == "ignore"
|
87
|
+
endpoint += (endpoint.match(/\?/) ? "&" : "?") + "access_token=" + @token
|
88
|
+
endpoint += "&as_user_id=" + @as_user_id.to_s if @as_user_id
|
89
|
+
end
|
90
|
+
(params || {}).each do |key, value|
|
91
|
+
if value.is_a?(Array)
|
92
|
+
key = key + "[]" unless key.match(/\[\]/)
|
93
|
+
value.each do |val|
|
94
|
+
endpoint += (endpoint.match(/\?/) ? "&" : "?") + "#{CGI.escape(key.to_s)}=#{CGI.escape(val.to_s)}"
|
95
|
+
end
|
96
|
+
else
|
97
|
+
endpoint += (endpoint.match(/\?/) ? "&" : "?") + "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
@uri = URI.parse(@host + endpoint)
|
101
|
+
@http = Net::HTTP.new(@uri.host, @uri.port)
|
102
|
+
@http.use_ssl = @uri.scheme == 'https'
|
103
|
+
@uri
|
104
|
+
end
|
105
|
+
|
106
|
+
def retrieve_response(response)
|
107
|
+
response_body = response.body
|
108
|
+
begin
|
109
|
+
response_status = response.status
|
110
|
+
if response_status.to_s.match(/3\d\d/)
|
111
|
+
raise ApiError.new("unexpected redirect to #{response.headers['Location']}")
|
112
|
+
end
|
113
|
+
|
114
|
+
json = JSON.parse(response_body) rescue { 'error' => 'invalid JSON' }
|
115
|
+
|
116
|
+
if !json.is_a?(Array)
|
117
|
+
if json['error']
|
118
|
+
raise ApiError.new(json['error'])
|
119
|
+
elsif json['errors']
|
120
|
+
raise ApiError.new(json['errors'])
|
121
|
+
elsif !response_status.to_s.match(/2\d\d/)
|
122
|
+
json['message'] ||= "unexpected error"
|
123
|
+
json['status'] ||= response_status.to_s
|
124
|
+
raise ApiError.new("#{json['status']} #{json['message']}")
|
125
|
+
end
|
126
|
+
else
|
127
|
+
json = ResultSet.new(self, json)
|
128
|
+
if response.headers['Link']
|
129
|
+
json.link = response.headers['Link']
|
130
|
+
json.next_endpoint = response.headers['Link'].split(/,/).detect { |rel| rel.match(/rel="next"/) }.split(/;/).first.strip[1..-2].sub(/https?:\/\/[^\/]+/, '') rescue nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
json
|
135
|
+
rescue Faraday::TimeoutError => e
|
136
|
+
raise ApiError.new("request timed out")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Semi-hack so I can write better specs
|
141
|
+
def get_request(endpoint)
|
142
|
+
Faraday.get(endpoint) do |req|
|
143
|
+
req.headers['User-Agent'] = 'CanvasAPI Ruby'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def get(endpoint, params = nil)
|
148
|
+
uri = generate_uri(endpoint, params)
|
149
|
+
response = Faraday.get(uri.to_s)
|
150
|
+
retrieve_response(response)
|
151
|
+
end
|
152
|
+
|
153
|
+
def delete(endpoint, params = {})
|
154
|
+
uri = generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
|
155
|
+
response = Faraday.delete(uri.to_s)
|
156
|
+
retrieve_response(response)
|
157
|
+
end
|
158
|
+
|
159
|
+
def put(endpoint, params = {})
|
160
|
+
uri = generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
|
161
|
+
puts "params: #{params}"
|
162
|
+
puts "json params: #{params.to_json}"
|
163
|
+
puts "uri: #{uri}"
|
164
|
+
puts "new"
|
165
|
+
response = Faraday.put(uri.to_s) do |req|
|
166
|
+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
167
|
+
req.headers['Authorization'] = "Bearer #{@token}"
|
168
|
+
req.body = URI.encode_www_form(clean_params(params))
|
169
|
+
end
|
170
|
+
retrieve_response(response)
|
171
|
+
end
|
172
|
+
|
173
|
+
def post(endpoint, params = {})
|
174
|
+
uri = generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
|
175
|
+
response = Faraday.post(uri.to_s) do |req|
|
176
|
+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
177
|
+
req.headers['Authorization'] = "Bearer #{@token}"
|
178
|
+
req.body = URI.encode_www_form(clean_params(params))
|
179
|
+
end
|
180
|
+
retrieve_response(response)
|
181
|
+
end
|
182
|
+
|
183
|
+
def post_multi(endpoint, params = {})
|
184
|
+
uri = generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
|
185
|
+
response = Faraday.post(uri.to_s) do |req|
|
186
|
+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
187
|
+
req.headers['Authorization'] = "Bearer #{@token}"
|
188
|
+
req.body = URI.encode_www_form(clean_params(params))
|
189
|
+
end
|
190
|
+
retrieve_response(response)
|
191
|
+
end
|
192
|
+
|
193
|
+
def clean_params(params, prefix = nil)
|
194
|
+
pairs = PairArray.new
|
195
|
+
|
196
|
+
params.each do |key, value|
|
197
|
+
key = prefix.nil? ? key.to_s : "#{prefix}[#{key}]"
|
198
|
+
|
199
|
+
if value.is_a?(Hash)
|
200
|
+
pairs.concat(clean_params(value, key))
|
201
|
+
elsif value.is_a?(Array)
|
202
|
+
value.each_with_index do |v, index|
|
203
|
+
pairs << ["#{key}[#{index}]", v.to_s]
|
204
|
+
end
|
205
|
+
else
|
206
|
+
pairs << [key, value.to_s]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
pairs
|
211
|
+
end
|
212
|
+
|
213
|
+
def upload_file_from_local(endpoint, file, opts = {})
|
214
|
+
raise "Missing File object" unless file.is_a?(File)
|
215
|
+
params = {
|
216
|
+
:size => file.size,
|
217
|
+
:name => opts[:name] || opts['name'] || File.basename(file.path),
|
218
|
+
:content_type => opts[:content_type] || opts['content_type'] || "application/octet-stream",
|
219
|
+
:on_duplicate => opts[:on_duplicate] || opts['on_duplicate']
|
220
|
+
}
|
221
|
+
if opts[:parent_folder_id] || opts['parent_folder_id']
|
222
|
+
params[:parent_folder_id] = opts[:parent_folder_id] || opts['parent_folder_id']
|
223
|
+
elsif opts[:parent_folder_path] || opts['parent_folder_path']
|
224
|
+
params[:parent_folder_path] = opts[:parent_folder_path] || opts['parent_folder_path']
|
225
|
+
end
|
226
|
+
|
227
|
+
res = post(endpoint, params)
|
228
|
+
if !res['upload_url']
|
229
|
+
raise ApiError.new("Unexpected error: #{res['message'] || 'no upload URL returned'}")
|
230
|
+
end
|
231
|
+
status_url = multipart_upload(res['upload_url'], res['upload_params'], params, file)
|
232
|
+
status_path = "/" + status_url.split(/\//, 4)[-1]
|
233
|
+
res = get(status_path)
|
234
|
+
res
|
235
|
+
end
|
236
|
+
|
237
|
+
def multipart_upload(url, upload_params, params, file)
|
238
|
+
response = Faraday.post(url) do |req|
|
239
|
+
req.headers['Authorization'] = "Bearer #{@token}"
|
240
|
+
req.headers['Content-Type'] = 'multipart/form-data'
|
241
|
+
req.body = {
|
242
|
+
'file' => Faraday::UploadIO.new(file.path, params[:content_type] || 'application/octet-stream'),
|
243
|
+
'size' => file.size.to_s,
|
244
|
+
'name' => params[:name] || File.basename(file.path),
|
245
|
+
'content_type' => params[:content_type] || 'application/octet-stream'
|
246
|
+
}.merge(upload_params)
|
247
|
+
end
|
248
|
+
|
249
|
+
if response.status == 201 && response.headers['Location']
|
250
|
+
response.headers['Location']
|
251
|
+
else
|
252
|
+
raise ApiError.new("Unexpected error: #{response.body}")
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def upload_file_from_url(endpoint, opts)
|
257
|
+
asynch = opts.delete('asynch') || opts.delete(:asynch)
|
258
|
+
['url', 'name', 'size'].each do |k|
|
259
|
+
raise "Missing value: #{k}" unless opts[k.to_s] || opts[k.to_sym]
|
260
|
+
end
|
261
|
+
|
262
|
+
res = post(endpoint, opts)
|
263
|
+
status_url = res['status_url']
|
264
|
+
if !status_url
|
265
|
+
raise ApiError.new("Unexpected error: #{res['message'] || 'no status URL returned'}")
|
266
|
+
end
|
267
|
+
status_path = "/" + status_url.split(/\//, 4)[-1]
|
268
|
+
if asynch
|
269
|
+
return status_path
|
270
|
+
else
|
271
|
+
attachment = nil
|
272
|
+
while !attachment
|
273
|
+
res = get(status_path)
|
274
|
+
if res['upload_status'] == 'errored'
|
275
|
+
raise ApiError.new(res['message'])
|
276
|
+
elsif !res['upload_status']
|
277
|
+
raise ApiError.new("Unexpected response")
|
278
|
+
end
|
279
|
+
status_path = res['status_path'] if res['status_path']
|
280
|
+
attachment = res['attachment']
|
281
|
+
sleep (defined?(SLEEP_TIME) ? SLEEP_TIME : 5) unless attachment
|
282
|
+
end
|
283
|
+
attachment
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
class ApiError < StandardError; end
|
289
|
+
|
290
|
+
class ResultSet < Array
|
291
|
+
def initialize(api, arr)
|
292
|
+
@api = api
|
293
|
+
super(arr)
|
294
|
+
end
|
295
|
+
|
296
|
+
attr_accessor :next_endpoint
|
297
|
+
attr_accessor :link
|
298
|
+
|
299
|
+
def more?
|
300
|
+
!!next_endpoint
|
301
|
+
end
|
302
|
+
|
303
|
+
def next_page!
|
304
|
+
ResultSet.new(@api, []) unless next_endpoint
|
305
|
+
more = @api.get(next_endpoint)
|
306
|
+
concat(more)
|
307
|
+
@next_endpoint = more.next_endpoint
|
308
|
+
@link = more.link
|
309
|
+
more
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class PairArray < Array
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# TODO: this is a hack that digs into the bowels of typhoeus
|
318
|
+
module Ethon
|
319
|
+
class Easy
|
320
|
+
module Queryable
|
321
|
+
def recursively_generate_pairs(h, prefix, pairs)
|
322
|
+
case h
|
323
|
+
when Hash
|
324
|
+
h.each_pair do |k, v|
|
325
|
+
key = prefix.nil? ? k : "#{prefix}[#{k}]"
|
326
|
+
pairs_for(v, key, pairs)
|
327
|
+
end
|
328
|
+
when Canvas::PairArray
|
329
|
+
h.each do |k, v|
|
330
|
+
key = prefix.nil? ? k : "#{prefix}[#{k}]"
|
331
|
+
pairs_for(v, key, pairs)
|
332
|
+
end
|
333
|
+
when Array
|
334
|
+
h.each_with_index do |v, i|
|
335
|
+
key = "#{prefix}[#{i}]"
|
336
|
+
pairs_for(v, key, pairs)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
metadata
CHANGED
@@ -1,35 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strongmind_canvas_api_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Strongmind
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-08-
|
11
|
+
date: 2023-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: faraday
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.1'
|
20
17
|
- - ">="
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
19
|
+
version: '0'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- - "~>"
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '1.1'
|
30
24
|
- - ">="
|
31
25
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
26
|
+
version: '0'
|
33
27
|
description: Canvas API Client for Strongmind
|
34
28
|
email:
|
35
29
|
- steakholders@strongmind.com
|
@@ -47,6 +41,7 @@ files:
|
|
47
41
|
- LICENSE.txt
|
48
42
|
- README.md
|
49
43
|
- Rakefile
|
44
|
+
- lib/canvas/canvas_api.rb
|
50
45
|
- lib/strongmind_canvas_api_client.rb
|
51
46
|
- lib/strongmind_canvas_api_client/version.rb
|
52
47
|
- sig/strongmind_canvas_api_client.rbs
|