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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecdc4d036984a624014281bb150f72e812e935e6e903e0823c8928e41e5dfbd2
4
- data.tar.gz: 969cb01b81fbecb2438135e98ade981a50270835726d6bbb9cfc19097b4bb3f7
3
+ metadata.gz: 7a031a2d0529c689391b4385842256b458a50d7d638be041da258672674ba1b0
4
+ data.tar.gz: b459407d9102314eb88f5968c696b9c6614797be12e5ff37047c3902a47c624f
5
5
  SHA512:
6
- metadata.gz: f9ca5c76ba446eda623435e3b166bb7186ffaef31fd9aa4978ec2085d950c558edaadd6c2ee23771bd0fcb95cc8e8563b7c12920e51d8b5d35d37af80a08d891
7
- data.tar.gz: 959dbae4ab4db96a551f14d433e4ee09fd844da383e75c77bc27dc63f9a44584e5cf84a1e8309f5cc7ef9069688b91bf63c8efdd7d1b3a1af269bc5804a3a360
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.0)
5
- canvas-api (~> 1.1, >= 1.1.1)
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
- ethon (0.15.0)
16
- ffi (>= 1.15.0)
17
- ffi (1.15.5)
18
- json (2.6.2)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StrongmindCanvasApiClient
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'canvas-api'
3
+ require_relative "canvas/canvas_api"
4
4
  require_relative "strongmind_canvas_api_client/version"
5
5
 
6
6
  module StrongmindCanvasApiClient
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.0
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-15 00:00:00.000000000 Z
11
+ date: 2023-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: canvas-api
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: 1.1.1
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: 1.1.1
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