strongmind_canvas_api_client 0.1.0 → 0.1.2

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: 5c21ebc3b9958074b5314f02d4407b1c89ea232ab46aef8237478ee976d78eda
4
+ data.tar.gz: 7d6424c32dd62fe250733798e3fa153a6bb6fd97b75615c47fa53b9f4954fa15
5
5
  SHA512:
6
- metadata.gz: f9ca5c76ba446eda623435e3b166bb7186ffaef31fd9aa4978ec2085d950c558edaadd6c2ee23771bd0fcb95cc8e8563b7c12920e51d8b5d35d37af80a08d891
7
- data.tar.gz: 959dbae4ab4db96a551f14d433e4ee09fd844da383e75c77bc27dc63f9a44584e5cf84a1e8309f5cc7ef9069688b91bf63c8efdd7d1b3a1af269bc5804a3a360
6
+ metadata.gz: 2b1139921197cbae32b762ec3b53e6e390f99077fbca6cf6ad0563ca35b90ab95e060d30f007a65ae80f655896b110ecc4f6cad9d9482f07545c0e66fd78ba64
7
+ data.tar.gz: 5806b7053e1b64f6f477a259ce2850855d78ab69be1d3c8948c08b355f59c09907b0d33cd0c0186fbc7a6485d541385bfa98868934a20231c3114a2058898464
data/Gemfile.lock CHANGED
@@ -1,21 +1,20 @@
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.2)
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.9.0)
13
+ faraday-net_http (>= 2.0, < 3.2)
14
+ faraday-net_http (3.1.0)
15
+ net-http
16
+ net-http (0.4.1)
17
+ uri
19
18
  parallel (1.22.1)
20
19
  parser (3.1.2.0)
21
20
  ast (~> 2.4.1)
@@ -54,9 +53,8 @@ GEM
54
53
  standard (1.12.1)
55
54
  rubocop (= 1.29.1)
56
55
  rubocop-performance (= 1.13.3)
57
- typhoeus (1.4.0)
58
- ethon (>= 0.9.0)
59
56
  unicode-display_width (2.1.0)
57
+ uri (0.13.0)
60
58
 
61
59
  PLATFORMS
62
60
  arm64-darwin-22
@@ -0,0 +1,350 @@
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
+ begin
155
+ uri = generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
156
+ response = Faraday.delete(uri.to_s)
157
+ retrieve_response(response)
158
+ rescue Faraday::Error => e
159
+ if e.message == 'The specified resource does not exist.'
160
+ e.message
161
+ else
162
+ raise e
163
+ end
164
+ end
165
+ end
166
+
167
+ def put(endpoint, params = {})
168
+ uri = generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
169
+ puts "params: #{params}"
170
+ puts "json params: #{params.to_json}"
171
+ puts "uri: #{uri}"
172
+ puts "new"
173
+ response = Faraday.put(uri.to_s) do |req|
174
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
175
+ req.headers['Authorization'] = "Bearer #{@token}"
176
+ req.body = URI.encode_www_form(clean_params(params))
177
+ end
178
+ retrieve_response(response)
179
+ end
180
+
181
+ def post(endpoint, params = {})
182
+ uri = generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
183
+ response = Faraday.post(uri.to_s) do |req|
184
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
185
+ req.headers['Authorization'] = "Bearer #{@token}"
186
+ req.body = URI.encode_www_form(clean_params(params))
187
+ end
188
+ retrieve_response(response)
189
+ end
190
+
191
+ def post_multi(endpoint, params = {})
192
+ uri = generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
193
+ response = Faraday.post(uri.to_s) do |req|
194
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
195
+ req.headers['Authorization'] = "Bearer #{@token}"
196
+ req.body = URI.encode_www_form(clean_params(params))
197
+ end
198
+ retrieve_response(response)
199
+ end
200
+
201
+ def clean_params(params, prefix = nil)
202
+ pairs = PairArray.new
203
+
204
+ params.each do |key, value|
205
+ key = prefix.nil? ? key.to_s : "#{prefix}[#{key}]"
206
+
207
+ if value.is_a?(Hash)
208
+ pairs.concat(clean_params(value, key))
209
+ elsif value.is_a?(Array)
210
+ value.each_with_index do |v, index|
211
+ pairs << ["#{key}[#{index}]", v.to_s]
212
+ end
213
+ else
214
+ pairs << [key, value.to_s]
215
+ end
216
+ end
217
+
218
+ pairs
219
+ end
220
+
221
+ def upload_file_from_local(endpoint, file, opts = {})
222
+ raise "Missing File object" unless file.is_a?(File)
223
+ params = {
224
+ :size => file.size,
225
+ :name => opts[:name] || opts['name'] || File.basename(file.path),
226
+ :content_type => opts[:content_type] || opts['content_type'] || "application/octet-stream",
227
+ :on_duplicate => opts[:on_duplicate] || opts['on_duplicate']
228
+ }
229
+ if opts[:parent_folder_id] || opts['parent_folder_id']
230
+ params[:parent_folder_id] = opts[:parent_folder_id] || opts['parent_folder_id']
231
+ elsif opts[:parent_folder_path] || opts['parent_folder_path']
232
+ params[:parent_folder_path] = opts[:parent_folder_path] || opts['parent_folder_path']
233
+ end
234
+
235
+ res = post(endpoint, params)
236
+ if !res['upload_url']
237
+ raise ApiError.new("Unexpected error: #{res['message'] || 'no upload URL returned'}")
238
+ end
239
+ status_url = multipart_upload(res['upload_url'], res['upload_params'], params, file)
240
+ status_path = "/" + status_url.split(/\//, 4)[-1]
241
+ res = get(status_path)
242
+ res
243
+ end
244
+
245
+ def multipart_upload(url, upload_params, params, file)
246
+ response = Faraday.post(url) do |req|
247
+ req.headers['Authorization'] = "Bearer #{@token}"
248
+ req.headers['Content-Type'] = 'multipart/form-data'
249
+ req.body = {
250
+ 'file' => Faraday::UploadIO.new(file.path, params[:content_type] || 'application/octet-stream'),
251
+ 'size' => file.size.to_s,
252
+ 'name' => params[:name] || File.basename(file.path),
253
+ 'content_type' => params[:content_type] || 'application/octet-stream'
254
+ }.merge(upload_params)
255
+ end
256
+
257
+ if response.status == 201 && response.headers['Location']
258
+ response.headers['Location']
259
+ else
260
+ raise ApiError.new("Unexpected error: #{response.body}")
261
+ end
262
+ end
263
+
264
+ def upload_file_from_url(endpoint, opts)
265
+ asynch = opts.delete('asynch') || opts.delete(:asynch)
266
+ ['url', 'name', 'size'].each do |k|
267
+ raise "Missing value: #{k}" unless opts[k.to_s] || opts[k.to_sym]
268
+ end
269
+
270
+ res = post(endpoint, opts)
271
+ status_url = res['status_url']
272
+ if !status_url
273
+ raise ApiError.new("Unexpected error: #{res['message'] || 'no status URL returned'}")
274
+ end
275
+ status_path = "/" + status_url.split(/\//, 4)[-1]
276
+ if asynch
277
+ return status_path
278
+ else
279
+ attachment = nil
280
+ while !attachment
281
+ res = get(status_path)
282
+ if res['upload_status'] == 'errored'
283
+ raise ApiError.new(res['message'])
284
+ elsif !res['upload_status']
285
+ raise ApiError.new("Unexpected response")
286
+ end
287
+ status_path = res['status_path'] if res['status_path']
288
+ attachment = res['attachment']
289
+ sleep (defined?(SLEEP_TIME) ? SLEEP_TIME : 5) unless attachment
290
+ end
291
+ attachment
292
+ end
293
+ end
294
+ end
295
+
296
+ class ApiError < StandardError; end
297
+
298
+ class ResultSet < Array
299
+ def initialize(api, arr)
300
+ @api = api
301
+ super(arr)
302
+ end
303
+
304
+ attr_accessor :next_endpoint
305
+ attr_accessor :link
306
+
307
+ def more?
308
+ !!next_endpoint
309
+ end
310
+
311
+ def next_page!
312
+ ResultSet.new(@api, []) unless next_endpoint
313
+ more = @api.get(next_endpoint)
314
+ concat(more)
315
+ @next_endpoint = more.next_endpoint
316
+ @link = more.link
317
+ more
318
+ end
319
+ end
320
+
321
+ class PairArray < Array
322
+ end
323
+ end
324
+
325
+ # TODO: this is a hack that digs into the bowels of typhoeus
326
+ module Ethon
327
+ class Easy
328
+ module Queryable
329
+ def recursively_generate_pairs(h, prefix, pairs)
330
+ case h
331
+ when Hash
332
+ h.each_pair do |k, v|
333
+ key = prefix.nil? ? k : "#{prefix}[#{k}]"
334
+ pairs_for(v, key, pairs)
335
+ end
336
+ when Canvas::PairArray
337
+ h.each do |k, v|
338
+ key = prefix.nil? ? k : "#{prefix}[#{k}]"
339
+ pairs_for(v, key, pairs)
340
+ end
341
+ when Array
342
+ h.each_with_index do |v, i|
343
+ key = "#{prefix}[#{i}]"
344
+ pairs_for(v, key, pairs)
345
+ end
346
+ end
347
+ end
348
+ end
349
+ end
350
+ 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.2"
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.2
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: 2024-04-03 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