bright 0.2.0 → 1.0

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.
@@ -108,11 +108,12 @@ module Bright
108
108
  else
109
109
  body = JSON.dump(params)
110
110
  end
111
- puts uri.inspect
112
- headers = self.headers_for_auth(uri)
113
111
 
114
- connection = Bright::Connection.new(uri)
115
- response = connection.request(method, body, headers)
112
+ response = connection_retry_wrapper {
113
+ connection = Bright::Connection.new(uri)
114
+ headers = self.headers_for_auth
115
+ connection.request(method, body, headers)
116
+ }
116
117
 
117
118
  if !response.error?
118
119
  response_hash = JSON.parse(response.body)
@@ -149,7 +150,6 @@ module Bright
149
150
 
150
151
  def convert_to_student_data(student_params)
151
152
  return {} if student_params.nil?
152
-
153
153
  student_data_hsh = {
154
154
  :api_id => student_params["uuid"],
155
155
  :first_name => student_params["first_name"],
@@ -165,7 +165,7 @@ module Bright
165
165
  :image => student_params["picture"],
166
166
  :hispanic_ethnicity => student_params["hispanic_latino"],
167
167
  :last_modified => student_params["updated_at"]
168
- }
168
+ }.reject{|k,v| v.blank?}
169
169
  unless student_params["birthdate"].blank?
170
170
  student_data_hsh[:birth_date] = Date.parse(student_params["birthdate"]).to_s
171
171
  end
@@ -177,19 +177,21 @@ module Bright
177
177
  end
178
178
  end
179
179
 
180
- unless student_params["student_street"].blank?
181
- student_data_hsh[:addresses] = [{
182
- :street => student_params["student_street"],
183
- :apt => student_params["student_street_line_2"],
184
- :city => student_params["student_city"],
185
- :state => student_params["student_state"],
186
- :postal_code => student_params["student_zip"]
187
- }]
180
+ unless student_params["addresses"].blank?
181
+ student_data_hsh[:addresses] = student_params["addresses"].collect do |address_params|
182
+ convert_to_address_data(address_params)
183
+ end
184
+ end
185
+
186
+ unless student_params["phone_numbers"].blank?
187
+ student_data_hsh[:phone_numbers] = student_params["phone_numbers"].collect do |phone_params|
188
+ convert_to_phone_number_data(phone_params)
189
+ end
188
190
  end
189
191
 
190
- unless student_params["student_email"].blank?
192
+ unless student_params["email_addresses"].blank?
191
193
  student_data_hsh[:email_address] = {
192
- :email_address => student_params["student_email"]
194
+ :email_address => student_params["email_addresses"].first["email_address"]
193
195
  }
194
196
  end
195
197
 
@@ -197,6 +199,36 @@ module Bright
197
199
  student_data_hsh[:school] = convert_to_school_data(student_params["school"])
198
200
  end
199
201
 
202
+ unless student_params["contacts"].blank?
203
+ student_data_hsh[:contacts] = student_params["contacts"].collect do |contact_params|
204
+ contact_data_hsh = {
205
+ :api_id => contact_params["uuid"],
206
+ :first_name => contact_params["first_name"],
207
+ :middle_name => contact_params["middle_name"],
208
+ :last_name => contact_params["last_name"],
209
+ :relationship_type => contact_params["relationship"],
210
+ :sis_student_id => contact_params["sis_id"],
211
+ :last_modified => contact_params["updated_at"]
212
+ }
213
+ unless contact_params["addresses"].blank?
214
+ contact_data_hsh[:addresses] = contact_params["addresses"].collect do |address_params|
215
+ convert_to_address_data(address_params)
216
+ end
217
+ end
218
+ unless contact_params["phone_numbers"].blank?
219
+ contact_data_hsh[:phone_numbers] = contact_params["phone_numbers"].collect do |phone_params|
220
+ convert_to_phone_number_data(phone_params)
221
+ end
222
+ end
223
+ unless contact_params["email_addresses"].blank?
224
+ contact_data_hsh[:email_address] = {
225
+ :email_address => contact_params["email_addresses"].first["email_address"]
226
+ }
227
+ end
228
+ contact_data_hsh.reject{|k,v| v.blank?}
229
+ end
230
+ end
231
+
200
232
  return student_data_hsh
201
233
  end
202
234
 
@@ -213,6 +245,28 @@ module Bright
213
245
  return filter_params
214
246
  end
215
247
 
248
+ def convert_to_phone_number_data(phone_number_params)
249
+ return {} if phone_number_params.nil?
250
+ {
251
+ :phone_number => phone_number_params["phone_number"],
252
+ :type => phone_number_params["phone_type"]
253
+ }.reject{|k,v| v.blank?}
254
+ end
255
+
256
+ def convert_to_address_data(address_params)
257
+ return {} if address_params.nil?
258
+ {
259
+ :street => address_params["street"],
260
+ :apt => address_params["street_line_2"],
261
+ :city => address_params["city"],
262
+ :state => address_params["state"],
263
+ :postal_code => address_params["zip"],
264
+ :lattitude => address_params["latitude"],
265
+ :longitude => address_params["longitude"],
266
+ :type => address_params["address_type"]
267
+ }.reject{|k,v| v.blank?}
268
+ end
269
+
216
270
  def convert_to_school_data(school_params)
217
271
  return {} if school_params.nil?
218
272
 
@@ -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
@@ -74,8 +90,47 @@ module Bright
74
90
  raise NotImplementedError
75
91
  end
76
92
 
77
- def get_schools(params)
78
- raise NotImplementedError
93
+ def get_school_by_api_id(api_id, params = {})
94
+ sc_hsh = self.request(:get, "schools/#{api_id}", params)
95
+ School.new(convert_to_school_data(sc_hsh["org"])) if sc_hsh and sc_hsh["org"]
96
+ end
97
+
98
+ def get_school(params = {}, options = {})
99
+ self.get_schools(params, options.merge(:limit => 1, :wrap_in_collection => false)).first
100
+ end
101
+
102
+ def get_schools(params = {}, options = {})
103
+ params[:limit] = params[:limit] || options[:limit] || 100
104
+ schools_response_hash = self.request(:get, 'schools', self.map_school_search_params(params))
105
+ total_results = schools_response_hash[:response_headers]["x-total-count"].to_i
106
+ if schools_response_hash and schools_response_hash["orgs"]
107
+ schools_hash = [schools_response_hash["orgs"]].flatten
108
+
109
+ schools = schools_hash.compact.collect {|sc_hsh|
110
+ School.new(convert_to_school_data(sc_hsh))
111
+ }
112
+ end
113
+ if options[:wrap_in_collection] != false
114
+ api = self
115
+ load_more_call = proc { |page|
116
+ # pages start at one, so add a page here
117
+ params[:offset] = (params[:limit].to_i * page)
118
+ api.get_schools(params, {:wrap_in_collection => false})
119
+ }
120
+ ResponseCollection.new({
121
+ :seed_page => schools,
122
+ :total => total_results,
123
+ :per_page => params[:limit],
124
+ :load_more_call => load_more_call
125
+ })
126
+ else
127
+ schools
128
+ end
129
+ end
130
+
131
+ def get_contact_by_api_id(api_id, params ={})
132
+ contact_hsh = self.request(:get, "users/#{api_id}", params)
133
+ Contact.new(convert_to_user_data(contact_hsh["user"], bright_type: "Contact")) if contact_hsh and contact_hsh["user"]
79
134
  end
80
135
 
81
136
  def request(method, path, params = {})
@@ -87,10 +142,12 @@ module Bright
87
142
  else
88
143
  body = JSON.dump(params)
89
144
  end
90
- headers = self.headers_for_auth(uri)
91
145
 
92
- connection = Bright::Connection.new(uri)
93
- response = connection.request(method, body, headers)
146
+ response = connection_retry_wrapper {
147
+ connection = Bright::Connection.new(uri)
148
+ headers = self.headers_for_auth(uri)
149
+ connection.request(method, body, headers)
150
+ }
94
151
 
95
152
  if !response.error?
96
153
  response_hash = JSON.parse(response.body)
@@ -105,14 +162,53 @@ module Bright
105
162
  protected
106
163
 
107
164
  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"]}
165
+ case api_version
166
+ when Gem::Version.new("1.1")
167
+ site = URI.parse(self.connection_options[:uri])
168
+ site = "#{site.scheme}://#{site.host}"
169
+ consumer = OAuth::Consumer.new(self.connection_options[:client_id], self.connection_options[:client_secret], { :site => site, :scheme => :header })
170
+ options = {:timestamp => Time.now.to_i, :nonce => SecureRandom.uuid}
171
+ {"Authorization" => consumer.create_signed_request(:get, uri, nil, options)["Authorization"]}
172
+ when Gem::Version.new("1.2")
173
+ if self.connection_options[:access_token].nil? or self.connection_options[:access_token_expires] < Time.now
174
+ self.retrieve_access_token
175
+ end
176
+ {
177
+ "Authorization" => "Bearer #{self.connection_options[:access_token]}",
178
+ "Accept" => "application/json;charset=UTF-8",
179
+ "Content-Type" =>"application/json;charset=UTF-8"
180
+ }
181
+ end
182
+ end
183
+
184
+ def retrieve_access_token
185
+ connection = Bright::Connection.new(self.connection_options[:token_uri])
186
+ response = connection.request(:post,
187
+ {
188
+ "grant_type" => "client_credentials",
189
+ "username" => self.connection_options[:client_id],
190
+ "password" => self.connection_options[:client_secret]
191
+ },
192
+ self.headers_for_access_token
193
+ )
194
+ if !response.error?
195
+ response_hash = JSON.parse(response.body)
196
+ end
197
+ if response_hash["access_token"]
198
+ self.connection_options[:access_token] = response_hash["access_token"]
199
+ self.connection_options[:access_token_expires] = (Time.now - 10) + response_hash["expires_in"]
200
+ end
201
+ response_hash
202
+ end
203
+
204
+ def headers_for_access_token
205
+ {
206
+ "Authorization" => "Basic #{Base64.strict_encode64("#{self.connection_options[:client_id]}:#{self.connection_options[:client_secret]}")}",
207
+ "Content-Type" => "application/x-www-form-urlencoded;charset=UTF-8"
208
+ }
113
209
  end
114
210
 
115
- def map_student_search_params(params)
211
+ def map_search_params(params)
116
212
  params = params.dup
117
213
  default_params = {}
118
214
 
@@ -127,6 +223,10 @@ module Bright
127
223
  filter << "email='#{v}'"
128
224
  when "student_id"
129
225
  filter << "identifier='#{v}'"
226
+ when "last_modified"
227
+ filter << "dateLastModified>='#{v.to_time.utc.xmlschema}'"
228
+ when "role"
229
+ filter << "role='#{v}'"
130
230
  else
131
231
  default_params[k] = v
132
232
  end
@@ -137,31 +237,149 @@ module Bright
137
237
  default_params.merge(params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
138
238
  end
139
239
 
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"]
240
+ def map_school_search_params(params)
241
+ params = params.dup
242
+ default_params = {}
243
+ filter = []
244
+ params.each do |k,v|
245
+ case k.to_s
246
+ when "number"
247
+ filter << "identifier='#{v}'"
248
+ when "last_modified"
249
+ filter << "dateLastModified>='#{v.to_time.utc.xmlschema}'"
250
+ else
251
+ default_params[k] = v
252
+ end
253
+ end
254
+ unless filter.empty?
255
+ params = {"filter" => filter.join(" AND ")}
256
+ end
257
+ default_params.merge(params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
258
+ end
143
259
 
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"]
260
+ def convert_to_school_data(school_params)
261
+ return {} if school_params.blank?
262
+ school_data_hsh = {
263
+ :api_id => school_params["sourcedId"],
264
+ :name => school_params["name"],
265
+ :number => school_params["identifier"],
266
+ :last_modified => school_params["dateLastModified"]
151
267
  }
152
- unless demographics_params["birthdate"].nil?
153
- student_data_hsh[:birth_date] = Date.parse(demographics_params["birthdate"]).to_s
268
+ return school_data_hsh
269
+ end
270
+
271
+ def convert_to_user_data(user_params, bright_type: "Student")
272
+ return {} if user_params.blank?
273
+ user_data_hsh = {
274
+ :api_id => user_params["sourcedId"],
275
+ :first_name => user_params["givenName"],
276
+ :middle_name => user_params["middleName"],
277
+ :last_name => user_params["familyName"],
278
+ :last_modified => user_params["dateLastModified"]
279
+ }.reject{|k,v| v.blank?}
280
+ unless user_params["identifier"].blank?
281
+ user_data_hsh[:sis_student_id] = user_params["identifier"]
282
+ end
283
+ unless user_params["userMasterIdentifier"].blank?
284
+ user_data_hsh[:state_student_id] = user_params["userMasterIdentifier"]
285
+ end
286
+ unless user_params["userIds"].blank?
287
+ if (state_id_hsh = user_params["userIds"].detect{|user_id_hsh| user_id_hsh["type"] == "stateID"})
288
+ user_data_hsh[:state_student_id] = state_id_hsh["identifier"]
289
+ end
290
+ end
291
+ unless user_params["email"].blank?
292
+ user_data_hsh[:email_address] = {
293
+ :email_address => user_params["email"]
294
+ }
154
295
  end
155
- unless demographics_params["sex"].to_s[0].nil?
156
- student_data_hsh[:gender] = demographics_params["sex"].to_s[0].upcase
296
+ unless user_params["orgs"].blank?
297
+ if (s = user_params["orgs"].detect{|org| org["href"] =~ /\/schools\//})
298
+ self.schools_cache ||= {}
299
+ if (attending_school = self.schools_cache[s["sourcedId"]]).nil?
300
+ attending_school = self.get_school_by_api_id(s["sourcedId"])
301
+ self.schools_cache[attending_school.api_id] = attending_school
302
+ end
303
+ end
304
+ if attending_school
305
+ user_data_hsh[:school] = attending_school
306
+ end
307
+ end
308
+ unless user_params["phone"].blank?
309
+ user_data_hsh[:phone_numbers] = [{:phone_number => user_params["phone"]}]
310
+ end
311
+ unless user_params["sms"].blank?
312
+ user_data_hsh[:phone_numbers] ||= []
313
+ user_data_hsh[:phone_numbers] << {:phone_number => user_params["sms"]}
314
+ end
315
+
316
+ #add the demographic information
317
+ demographics_hash = get_demographic_information(user_data_hsh[:api_id])
318
+ user_data_hsh.merge!(demographics_hash) unless demographics_hash.blank?
319
+
320
+ #if you're a student, build the contacts too
321
+ if bright_type == "Student" and !user_params["agents"].blank?
322
+ user_data_hsh[:contacts] = user_params["agents"].collect do |agent_hsh|
323
+ begin
324
+ self.get_contact_by_api_id(agent_hsh["sourcedId"])
325
+ rescue Bright::ResponseError => e
326
+ if !e.message.to_s.include?("404")
327
+ raise e
328
+ end
329
+ end
330
+ end.compact
331
+ user_data_hsh[:grade] = (user_params["grades"] || []).first
332
+ if !user_data_hsh[:grade].blank?
333
+ user_data_hsh[:grade_school_year] = get_grade_school_year
334
+ end
335
+ end
336
+
337
+ return user_data_hsh
338
+ end
339
+
340
+ def get_demographic_information(api_id)
341
+ demographic_hsh = {}
342
+
343
+ begin
344
+ demographics_params = request(:get, "demographics/#{api_id}")["demographics"]
345
+ rescue Bright::ResponseError => e
346
+ if e.message.to_s.include?('404')
347
+ return demographic_hsh
348
+ else
349
+ raise e
350
+ end
351
+ end
352
+
353
+ unless (bday = demographics_params["birthdate"] || demographics_params["birthDate"]).blank?
354
+ demographic_hsh[:birth_date] = Date.parse(bday).to_s
355
+ end
356
+ unless demographics_params["sex"].to_s[0].blank?
357
+ demographic_hsh[:gender] = demographics_params["sex"].to_s[0].upcase
157
358
  end
158
359
  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
360
+ if demographics_params[demographics_key].to_bool
361
+ if demographics_value == "Hispanic Or Latino"
362
+ demographic_hsh[:hispanic_ethnicity] = true
363
+ else
364
+ demographic_hsh[:race] ||= []
365
+ demographic_hsh[:race] << demographics_value
366
+ end
367
+ end
368
+ end
369
+ return demographic_hsh
370
+ end
371
+
372
+ def get_grade_school_year(date = Date.today)
373
+ #return the school year of a specific date
374
+ self.school_years_cache ||= {}
375
+ if self.school_years_cache[date].nil?
376
+ academic_periods_params = self.request(:get, "academicSessions", {"filter" => "startDate<='#{date.to_s}' AND endDate>='#{date.to_s}' AND status='active'"})["academicSessions"]
377
+ school_years = academic_periods_params.map{|ap| ap["schoolYear"]}.uniq
378
+ if school_years.size == 1
379
+ self.school_years_cache[date] = school_years.first
162
380
  end
163
381
  end
164
- return student_data_hsh
382
+ return self.school_years_cache[date]
165
383
  end
166
384
 
167
385
  end