bright 0.2.0 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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