bright 0.2.0 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,10 +5,10 @@ module Bright
5
5
  class InfiniteCampus < Base
6
6
 
7
7
  @@description = "Connects to the Infinite Campus OneRoster API for accessing student information"
8
- @@doc_url = "https://content.infinitecampus.com/sis/Campus.1633/documentation/oneroster-api/"
9
- @@api_version = "v1.1"
8
+ @@doc_url = "https://content.infinitecampus.com/sis/latest/documentation/oneroster-api"
9
+ @@api_version = "1.1"
10
10
 
11
- attr_accessor :connection_options
11
+ attr_accessor :connection_options, :schools_cache, :school_years_cache
12
12
 
13
13
  DEMOGRAPHICS_CONVERSION = {
14
14
  "americanIndianOrAlaskaNative"=>"American Indian Or Alaska Native",
@@ -24,13 +24,24 @@ module Bright
24
24
  # {
25
25
  # :client_id => "",
26
26
  # :client_secret => "",
27
- # :uri => ""
27
+ # :api_version => "", (defaults to @@api_version)
28
+ # :uri => "",
29
+ # :token_uri => "" (api_version 1.2 required)
28
30
  # }
29
31
  end
30
32
 
33
+ def api_version
34
+ Gem::Version.new(self.connection_options.dig(:api_version) || @@api_version)
35
+ end
36
+
31
37
  def get_student_by_api_id(api_id, params = {})
38
+ if api_version <= Gem::Version.new("1.1")
39
+ params = {:role => "student"}.merge(params)
40
+ else
41
+ params = {:roles => "student"}.merge(params)
42
+ end
32
43
  st_hsh = self.request(:get, "users/#{api_id}", params)
33
- Student.new(convert_to_student_data(st_hsh["user"])) if st_hsh and st_hsh["user"]
44
+ Student.new(convert_to_user_data(st_hsh["user"])) if st_hsh and st_hsh["user"]
34
45
  end
35
46
 
36
47
  def get_student(params = {}, options = {})
@@ -38,14 +49,19 @@ module Bright
38
49
  end
39
50
 
40
51
  def get_students(params = {}, options = {})
52
+ if api_version <= Gem::Version.new("1.1")
53
+ params = {:role => "student"}.merge(params)
54
+ else
55
+ params = {:roles => "student"}.merge(params)
56
+ end
41
57
  params[:limit] = params[:limit] || options[:limit] || 100
42
- students_response_hash = self.request(:get, 'users', self.map_student_search_params(params))
58
+ students_response_hash = self.request(:get, 'users', self.map_search_params(params))
43
59
  total_results = students_response_hash[:response_headers]["x-total-count"].to_i
44
60
  if students_response_hash and students_response_hash["users"]
45
61
  students_hash = [students_response_hash["users"]].flatten
46
62
 
47
63
  students = students_hash.compact.collect {|st_hsh|
48
- Student.new(convert_to_student_data(st_hsh))
64
+ Student.new(convert_to_user_data(st_hsh))
49
65
  }
50
66
  end
51
67
  if options[:wrap_in_collection] != false
@@ -59,7 +75,8 @@ module Bright
59
75
  :seed_page => students,
60
76
  :total => total_results,
61
77
  :per_page => params[:limit],
62
- :load_more_call => load_more_call
78
+ :load_more_call => load_more_call,
79
+ :no_threads => options[:no_threads]
63
80
  })
64
81
  else
65
82
  students
@@ -74,8 +91,48 @@ module Bright
74
91
  raise NotImplementedError
75
92
  end
76
93
 
77
- def get_schools(params)
78
- raise NotImplementedError
94
+ def get_school_by_api_id(api_id, params = {})
95
+ sc_hsh = self.request(:get, "schools/#{api_id}", params)
96
+ School.new(convert_to_school_data(sc_hsh["org"])) if sc_hsh and sc_hsh["org"]
97
+ end
98
+
99
+ def get_school(params = {}, options = {})
100
+ self.get_schools(params, options.merge(:limit => 1, :wrap_in_collection => false)).first
101
+ end
102
+
103
+ def get_schools(params = {}, options = {})
104
+ params[:limit] = params[:limit] || options[:limit] || 100
105
+ schools_response_hash = self.request(:get, 'schools', self.map_school_search_params(params))
106
+ total_results = schools_response_hash[:response_headers]["x-total-count"].to_i
107
+ if schools_response_hash and schools_response_hash["orgs"]
108
+ schools_hash = [schools_response_hash["orgs"]].flatten
109
+
110
+ schools = schools_hash.compact.collect {|sc_hsh|
111
+ School.new(convert_to_school_data(sc_hsh))
112
+ }
113
+ end
114
+ if options[:wrap_in_collection] != false
115
+ api = self
116
+ load_more_call = proc { |page|
117
+ # pages start at one, so add a page here
118
+ params[:offset] = (params[:limit].to_i * page)
119
+ api.get_schools(params, {:wrap_in_collection => false})
120
+ }
121
+ ResponseCollection.new({
122
+ :seed_page => schools,
123
+ :total => total_results,
124
+ :per_page => params[:limit],
125
+ :load_more_call => load_more_call,
126
+ :no_threads => options[:no_threads]
127
+ })
128
+ else
129
+ schools
130
+ end
131
+ end
132
+
133
+ def get_contact_by_api_id(api_id, params ={})
134
+ contact_hsh = self.request(:get, "users/#{api_id}", params)
135
+ Contact.new(convert_to_user_data(contact_hsh["user"], bright_type: "Contact")) if contact_hsh and contact_hsh["user"]
79
136
  end
80
137
 
81
138
  def request(method, path, params = {})
@@ -87,10 +144,12 @@ module Bright
87
144
  else
88
145
  body = JSON.dump(params)
89
146
  end
90
- headers = self.headers_for_auth(uri)
91
147
 
92
- connection = Bright::Connection.new(uri)
93
- response = connection.request(method, body, headers)
148
+ response = connection_retry_wrapper {
149
+ connection = Bright::Connection.new(uri)
150
+ headers = self.headers_for_auth(uri)
151
+ connection.request(method, body, headers)
152
+ }
94
153
 
95
154
  if !response.error?
96
155
  response_hash = JSON.parse(response.body)
@@ -105,14 +164,53 @@ module Bright
105
164
  protected
106
165
 
107
166
  def headers_for_auth(uri)
108
- site = URI.parse(self.connection_options[:uri])
109
- site = "#{site.scheme}://#{site.host}"
110
- consumer = OAuth::Consumer.new(self.connection_options[:client_id], self.connection_options[:client_secret], { :site => site, :scheme => :header })
111
- options = {:timestamp => Time.now.to_i, :nonce => SecureRandom.uuid}
112
- {"Authorization" => consumer.create_signed_request(:get, uri, nil, options)["Authorization"]}
167
+ case api_version
168
+ when Gem::Version.new("1.1")
169
+ site = URI.parse(self.connection_options[:uri])
170
+ site = "#{site.scheme}://#{site.host}"
171
+ consumer = OAuth::Consumer.new(self.connection_options[:client_id], self.connection_options[:client_secret], { :site => site, :scheme => :header })
172
+ options = {:timestamp => Time.now.to_i, :nonce => SecureRandom.uuid}
173
+ {"Authorization" => consumer.create_signed_request(:get, uri, nil, options)["Authorization"]}
174
+ when Gem::Version.new("1.2")
175
+ if self.connection_options[:access_token].nil? or self.connection_options[:access_token_expires] < Time.now
176
+ self.retrieve_access_token
177
+ end
178
+ {
179
+ "Authorization" => "Bearer #{self.connection_options[:access_token]}",
180
+ "Accept" => "application/json;charset=UTF-8",
181
+ "Content-Type" =>"application/json;charset=UTF-8"
182
+ }
183
+ end
113
184
  end
114
185
 
115
- def map_student_search_params(params)
186
+ def retrieve_access_token
187
+ connection = Bright::Connection.new(self.connection_options[:token_uri])
188
+ response = connection.request(:post,
189
+ {
190
+ "grant_type" => "client_credentials",
191
+ "username" => self.connection_options[:client_id],
192
+ "password" => self.connection_options[:client_secret]
193
+ },
194
+ self.headers_for_access_token
195
+ )
196
+ if !response.error?
197
+ response_hash = JSON.parse(response.body)
198
+ end
199
+ if response_hash["access_token"]
200
+ self.connection_options[:access_token] = response_hash["access_token"]
201
+ self.connection_options[:access_token_expires] = (Time.now - 10) + response_hash["expires_in"]
202
+ end
203
+ response_hash
204
+ end
205
+
206
+ def headers_for_access_token
207
+ {
208
+ "Authorization" => "Basic #{Base64.strict_encode64("#{self.connection_options[:client_id]}:#{self.connection_options[:client_secret]}")}",
209
+ "Content-Type" => "application/x-www-form-urlencoded;charset=UTF-8"
210
+ }
211
+ end
212
+
213
+ def map_search_params(params)
116
214
  params = params.dup
117
215
  default_params = {}
118
216
 
@@ -127,6 +225,10 @@ module Bright
127
225
  filter << "email='#{v}'"
128
226
  when "student_id"
129
227
  filter << "identifier='#{v}'"
228
+ when "last_modified"
229
+ filter << "dateLastModified>='#{v.to_time.utc.xmlschema}'"
230
+ when "role"
231
+ filter << "role='#{v}'"
130
232
  else
131
233
  default_params[k] = v
132
234
  end
@@ -137,31 +239,149 @@ module Bright
137
239
  default_params.merge(params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
138
240
  end
139
241
 
140
- def convert_to_student_data(student_params)
141
- return {} if student_params.nil?
142
- demographics_params = self.request(:get, "demographics/#{student_params["sourcedId"]}")["demographics"]
242
+ def map_school_search_params(params)
243
+ params = params.dup
244
+ default_params = {}
245
+ filter = []
246
+ params.each do |k,v|
247
+ case k.to_s
248
+ when "number"
249
+ filter << "identifier='#{v}'"
250
+ when "last_modified"
251
+ filter << "dateLastModified>='#{v.to_time.utc.xmlschema}'"
252
+ else
253
+ default_params[k] = v
254
+ end
255
+ end
256
+ unless filter.empty?
257
+ params = {"filter" => filter.join(" AND ")}
258
+ end
259
+ default_params.merge(params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
260
+ end
143
261
 
144
- student_data_hsh = {
145
- :api_id => student_params["sourcedId"],
146
- :first_name => student_params["givenName"],
147
- :middle_name => student_params["middleName"],
148
- :last_name => student_params["familyName"],
149
- :sis_student_id => student_params["identifier"],
150
- :last_modified => student_params["dateLastModified"]
262
+ def convert_to_school_data(school_params)
263
+ return {} if school_params.blank?
264
+ school_data_hsh = {
265
+ :api_id => school_params["sourcedId"],
266
+ :name => school_params["name"],
267
+ :number => school_params["identifier"],
268
+ :last_modified => school_params["dateLastModified"]
151
269
  }
152
- unless demographics_params["birthdate"].nil?
153
- student_data_hsh[:birth_date] = Date.parse(demographics_params["birthdate"]).to_s
270
+ return school_data_hsh
271
+ end
272
+
273
+ def convert_to_user_data(user_params, bright_type: "Student")
274
+ return {} if user_params.blank?
275
+ user_data_hsh = {
276
+ :api_id => user_params["sourcedId"],
277
+ :first_name => user_params["givenName"],
278
+ :middle_name => user_params["middleName"],
279
+ :last_name => user_params["familyName"],
280
+ :last_modified => user_params["dateLastModified"]
281
+ }.reject{|k,v| v.blank?}
282
+ unless user_params["identifier"].blank?
283
+ user_data_hsh[:sis_student_id] = user_params["identifier"]
154
284
  end
155
- unless demographics_params["sex"].to_s[0].nil?
156
- student_data_hsh[:gender] = demographics_params["sex"].to_s[0].upcase
285
+ unless user_params["userMasterIdentifier"].blank?
286
+ user_data_hsh[:state_student_id] = user_params["userMasterIdentifier"]
287
+ end
288
+ unless user_params["userIds"].blank?
289
+ if (state_id_hsh = user_params["userIds"].detect{|user_id_hsh| user_id_hsh["type"] == "stateID"})
290
+ user_data_hsh[:state_student_id] = state_id_hsh["identifier"]
291
+ end
292
+ end
293
+ unless user_params["email"].blank?
294
+ user_data_hsh[:email_address] = {
295
+ :email_address => user_params["email"]
296
+ }
297
+ end
298
+ unless user_params["orgs"].blank?
299
+ if (s = user_params["orgs"].detect{|org| org["href"] =~ /\/schools\//})
300
+ self.schools_cache ||= {}
301
+ if (attending_school = self.schools_cache[s["sourcedId"]]).nil?
302
+ attending_school = self.get_school_by_api_id(s["sourcedId"])
303
+ self.schools_cache[attending_school.api_id] = attending_school
304
+ end
305
+ end
306
+ if attending_school
307
+ user_data_hsh[:school] = attending_school
308
+ end
309
+ end
310
+ unless user_params["phone"].blank?
311
+ user_data_hsh[:phone_numbers] = [{:phone_number => user_params["phone"]}]
312
+ end
313
+ unless user_params["sms"].blank?
314
+ user_data_hsh[:phone_numbers] ||= []
315
+ user_data_hsh[:phone_numbers] << {:phone_number => user_params["sms"]}
316
+ end
317
+
318
+ #add the demographic information
319
+ demographics_hash = get_demographic_information(user_data_hsh[:api_id])
320
+ user_data_hsh.merge!(demographics_hash) unless demographics_hash.blank?
321
+
322
+ #if you're a student, build the contacts too
323
+ if bright_type == "Student" and !user_params["agents"].blank?
324
+ user_data_hsh[:contacts] = user_params["agents"].collect do |agent_hsh|
325
+ begin
326
+ self.get_contact_by_api_id(agent_hsh["sourcedId"])
327
+ rescue Bright::ResponseError => e
328
+ if !e.message.to_s.include?("404")
329
+ raise e
330
+ end
331
+ end
332
+ end.compact
333
+ user_data_hsh[:grade] = (user_params["grades"] || []).first
334
+ if !user_data_hsh[:grade].blank?
335
+ user_data_hsh[:grade_school_year] = get_grade_school_year
336
+ end
337
+ end
338
+
339
+ return user_data_hsh
340
+ end
341
+
342
+ def get_demographic_information(api_id)
343
+ demographic_hsh = {}
344
+
345
+ begin
346
+ demographics_params = request(:get, "demographics/#{api_id}")["demographics"]
347
+ rescue Bright::ResponseError => e
348
+ if e.message.to_s.include?('404')
349
+ return demographic_hsh
350
+ else
351
+ raise e
352
+ end
353
+ end
354
+
355
+ unless (bday = demographics_params["birthdate"] || demographics_params["birthDate"]).blank?
356
+ demographic_hsh[:birth_date] = Date.parse(bday).to_s
357
+ end
358
+ unless demographics_params["sex"].to_s[0].blank?
359
+ demographic_hsh[:gender] = demographics_params["sex"].to_s[0].upcase
157
360
  end
158
361
  DEMOGRAPHICS_CONVERSION.each do |demographics_key, demographics_value|
159
- if demographics_params[demographics_key] == "true"
160
- student_data_hsh[:race] ||= []
161
- student_data_hsh[:race] << demographics_value
362
+ if demographics_params[demographics_key].to_bool
363
+ if demographics_value == "Hispanic Or Latino"
364
+ demographic_hsh[:hispanic_ethnicity] = true
365
+ else
366
+ demographic_hsh[:race] ||= []
367
+ demographic_hsh[:race] << demographics_value
368
+ end
369
+ end
370
+ end
371
+ return demographic_hsh
372
+ end
373
+
374
+ def get_grade_school_year(date = Date.today)
375
+ #return the school year of a specific date
376
+ self.school_years_cache ||= {}
377
+ if self.school_years_cache[date].nil?
378
+ academic_periods_params = self.request(:get, "academicSessions", {"filter" => "startDate<='#{date.to_s}' AND endDate>='#{date.to_s}' AND status='active'"})["academicSessions"]
379
+ school_years = academic_periods_params.map{|ap| ap["schoolYear"]}.uniq
380
+ if school_years.size == 1
381
+ self.school_years_cache[date] = school_years.first
162
382
  end
163
383
  end
164
- return student_data_hsh
384
+ return self.school_years_cache[date]
165
385
  end
166
386
 
167
387
  end