lib/bright/sis_apis/infinite_campus.rb in bright-0.2.0 vs lib/bright/sis_apis/infinite_campus.rb in bright-1.0
- old
+ new
@@ -3,14 +3,14 @@
module Bright
module SisApi
class InfiniteCampus < Base
@@description = "Connects to the Infinite Campus OneRoster API for accessing student information"
- @@doc_url = "https://content.infinitecampus.com/sis/Campus.1633/documentation/oneroster-api/"
- @@api_version = "v1.1"
+ @@doc_url = "https://content.infinitecampus.com/sis/latest/documentation/oneroster-api"
+ @@api_version = "1.1"
- attr_accessor :connection_options
+ attr_accessor :connection_options, :schools_cache, :school_years_cache
DEMOGRAPHICS_CONVERSION = {
"americanIndianOrAlaskaNative"=>"American Indian Or Alaska Native",
"asian"=>"Asian",
"blackOrAfricanAmerican"=>"Black Or African American",
@@ -22,32 +22,48 @@
def initialize(options = {})
self.connection_options = options[:connection] || {}
# {
# :client_id => "",
# :client_secret => "",
- # :uri => ""
+ # :api_version => "", (defaults to @@api_version)
+ # :uri => "",
+ # :token_uri => "" (api_version 1.2 required)
# }
end
+ def api_version
+ Gem::Version.new(self.connection_options.dig(:api_version) || @@api_version)
+ end
+
def get_student_by_api_id(api_id, params = {})
+ if api_version <= Gem::Version.new("1.1")
+ params = {:role => "student"}.merge(params)
+ else
+ params = {:roles => "student"}.merge(params)
+ end
st_hsh = self.request(:get, "users/#{api_id}", params)
- Student.new(convert_to_student_data(st_hsh["user"])) if st_hsh and st_hsh["user"]
+ Student.new(convert_to_user_data(st_hsh["user"])) if st_hsh and st_hsh["user"]
end
def get_student(params = {}, options = {})
self.get_students(params, options.merge(:limit => 1, :wrap_in_collection => false)).first
end
def get_students(params = {}, options = {})
+ if api_version <= Gem::Version.new("1.1")
+ params = {:role => "student"}.merge(params)
+ else
+ params = {:roles => "student"}.merge(params)
+ end
params[:limit] = params[:limit] || options[:limit] || 100
- students_response_hash = self.request(:get, 'users', self.map_student_search_params(params))
+ students_response_hash = self.request(:get, 'users', self.map_search_params(params))
total_results = students_response_hash[:response_headers]["x-total-count"].to_i
if students_response_hash and students_response_hash["users"]
students_hash = [students_response_hash["users"]].flatten
students = students_hash.compact.collect {|st_hsh|
- Student.new(convert_to_student_data(st_hsh))
+ Student.new(convert_to_user_data(st_hsh))
}
end
if options[:wrap_in_collection] != false
api = self
load_more_call = proc { |page|
@@ -72,27 +88,68 @@
def update_student(student)
raise NotImplementedError
end
- def get_schools(params)
- raise NotImplementedError
+ def get_school_by_api_id(api_id, params = {})
+ sc_hsh = self.request(:get, "schools/#{api_id}", params)
+ School.new(convert_to_school_data(sc_hsh["org"])) if sc_hsh and sc_hsh["org"]
end
+ def get_school(params = {}, options = {})
+ self.get_schools(params, options.merge(:limit => 1, :wrap_in_collection => false)).first
+ end
+
+ def get_schools(params = {}, options = {})
+ params[:limit] = params[:limit] || options[:limit] || 100
+ schools_response_hash = self.request(:get, 'schools', self.map_school_search_params(params))
+ total_results = schools_response_hash[:response_headers]["x-total-count"].to_i
+ if schools_response_hash and schools_response_hash["orgs"]
+ schools_hash = [schools_response_hash["orgs"]].flatten
+
+ schools = schools_hash.compact.collect {|sc_hsh|
+ School.new(convert_to_school_data(sc_hsh))
+ }
+ end
+ if options[:wrap_in_collection] != false
+ api = self
+ load_more_call = proc { |page|
+ # pages start at one, so add a page here
+ params[:offset] = (params[:limit].to_i * page)
+ api.get_schools(params, {:wrap_in_collection => false})
+ }
+ ResponseCollection.new({
+ :seed_page => schools,
+ :total => total_results,
+ :per_page => params[:limit],
+ :load_more_call => load_more_call
+ })
+ else
+ schools
+ end
+ end
+
+ def get_contact_by_api_id(api_id, params ={})
+ contact_hsh = self.request(:get, "users/#{api_id}", params)
+ Contact.new(convert_to_user_data(contact_hsh["user"], bright_type: "Contact")) if contact_hsh and contact_hsh["user"]
+ end
+
def request(method, path, params = {})
uri = "#{self.connection_options[:uri]}/#{path}"
body = nil
if method == :get
query = URI.encode_www_form(params)
uri += "?#{query}" unless query.strip == ""
else
body = JSON.dump(params)
end
- headers = self.headers_for_auth(uri)
- connection = Bright::Connection.new(uri)
- response = connection.request(method, body, headers)
+ response = connection_retry_wrapper {
+ connection = Bright::Connection.new(uri)
+ headers = self.headers_for_auth(uri)
+ connection.request(method, body, headers)
+ }
if !response.error?
response_hash = JSON.parse(response.body)
response_hash[:response_headers] = response.headers
else
@@ -103,18 +160,57 @@
end
protected
def headers_for_auth(uri)
- site = URI.parse(self.connection_options[:uri])
- site = "#{site.scheme}://#{site.host}"
- consumer = OAuth::Consumer.new(self.connection_options[:client_id], self.connection_options[:client_secret], { :site => site, :scheme => :header })
- options = {:timestamp => Time.now.to_i, :nonce => SecureRandom.uuid}
- {"Authorization" => consumer.create_signed_request(:get, uri, nil, options)["Authorization"]}
+ case api_version
+ when Gem::Version.new("1.1")
+ site = URI.parse(self.connection_options[:uri])
+ site = "#{site.scheme}://#{site.host}"
+ consumer = OAuth::Consumer.new(self.connection_options[:client_id], self.connection_options[:client_secret], { :site => site, :scheme => :header })
+ options = {:timestamp => Time.now.to_i, :nonce => SecureRandom.uuid}
+ {"Authorization" => consumer.create_signed_request(:get, uri, nil, options)["Authorization"]}
+ when Gem::Version.new("1.2")
+ if self.connection_options[:access_token].nil? or self.connection_options[:access_token_expires] < Time.now
+ self.retrieve_access_token
+ end
+ {
+ "Authorization" => "Bearer #{self.connection_options[:access_token]}",
+ "Accept" => "application/json;charset=UTF-8",
+ "Content-Type" =>"application/json;charset=UTF-8"
+ }
+ end
end
- def map_student_search_params(params)
+ def retrieve_access_token
+ connection = Bright::Connection.new(self.connection_options[:token_uri])
+ response = connection.request(:post,
+ {
+ "grant_type" => "client_credentials",
+ "username" => self.connection_options[:client_id],
+ "password" => self.connection_options[:client_secret]
+ },
+ self.headers_for_access_token
+ )
+ if !response.error?
+ response_hash = JSON.parse(response.body)
+ end
+ if response_hash["access_token"]
+ self.connection_options[:access_token] = response_hash["access_token"]
+ self.connection_options[:access_token_expires] = (Time.now - 10) + response_hash["expires_in"]
+ end
+ response_hash
+ end
+
+ def headers_for_access_token
+ {
+ "Authorization" => "Basic #{Base64.strict_encode64("#{self.connection_options[:client_id]}:#{self.connection_options[:client_secret]}")}",
+ "Content-Type" => "application/x-www-form-urlencoded;charset=UTF-8"
+ }
+ end
+
+ def map_search_params(params)
params = params.dup
default_params = {}
filter = []
params.each do |k,v|
@@ -125,44 +221,166 @@
filter << "familyName='#{v}'"
when "email"
filter << "email='#{v}'"
when "student_id"
filter << "identifier='#{v}'"
+ when "last_modified"
+ filter << "dateLastModified>='#{v.to_time.utc.xmlschema}'"
+ when "role"
+ filter << "role='#{v}'"
else
default_params[k] = v
end
end
unless filter.empty?
params = {"filter" => filter.join(" AND ")}
end
default_params.merge(params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
end
- def convert_to_student_data(student_params)
- return {} if student_params.nil?
- demographics_params = self.request(:get, "demographics/#{student_params["sourcedId"]}")["demographics"]
+ def map_school_search_params(params)
+ params = params.dup
+ default_params = {}
+ filter = []
+ params.each do |k,v|
+ case k.to_s
+ when "number"
+ filter << "identifier='#{v}'"
+ when "last_modified"
+ filter << "dateLastModified>='#{v.to_time.utc.xmlschema}'"
+ else
+ default_params[k] = v
+ end
+ end
+ unless filter.empty?
+ params = {"filter" => filter.join(" AND ")}
+ end
+ default_params.merge(params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
+ end
- student_data_hsh = {
- :api_id => student_params["sourcedId"],
- :first_name => student_params["givenName"],
- :middle_name => student_params["middleName"],
- :last_name => student_params["familyName"],
- :sis_student_id => student_params["identifier"],
- :last_modified => student_params["dateLastModified"]
+ def convert_to_school_data(school_params)
+ return {} if school_params.blank?
+ school_data_hsh = {
+ :api_id => school_params["sourcedId"],
+ :name => school_params["name"],
+ :number => school_params["identifier"],
+ :last_modified => school_params["dateLastModified"]
}
- unless demographics_params["birthdate"].nil?
- student_data_hsh[:birth_date] = Date.parse(demographics_params["birthdate"]).to_s
+ return school_data_hsh
+ end
+
+ def convert_to_user_data(user_params, bright_type: "Student")
+ return {} if user_params.blank?
+ user_data_hsh = {
+ :api_id => user_params["sourcedId"],
+ :first_name => user_params["givenName"],
+ :middle_name => user_params["middleName"],
+ :last_name => user_params["familyName"],
+ :last_modified => user_params["dateLastModified"]
+ }.reject{|k,v| v.blank?}
+ unless user_params["identifier"].blank?
+ user_data_hsh[:sis_student_id] = user_params["identifier"]
end
- unless demographics_params["sex"].to_s[0].nil?
- student_data_hsh[:gender] = demographics_params["sex"].to_s[0].upcase
+ unless user_params["userMasterIdentifier"].blank?
+ user_data_hsh[:state_student_id] = user_params["userMasterIdentifier"]
end
+ unless user_params["userIds"].blank?
+ if (state_id_hsh = user_params["userIds"].detect{|user_id_hsh| user_id_hsh["type"] == "stateID"})
+ user_data_hsh[:state_student_id] = state_id_hsh["identifier"]
+ end
+ end
+ unless user_params["email"].blank?
+ user_data_hsh[:email_address] = {
+ :email_address => user_params["email"]
+ }
+ end
+ unless user_params["orgs"].blank?
+ if (s = user_params["orgs"].detect{|org| org["href"] =~ /\/schools\//})
+ self.schools_cache ||= {}
+ if (attending_school = self.schools_cache[s["sourcedId"]]).nil?
+ attending_school = self.get_school_by_api_id(s["sourcedId"])
+ self.schools_cache[attending_school.api_id] = attending_school
+ end
+ end
+ if attending_school
+ user_data_hsh[:school] = attending_school
+ end
+ end
+ unless user_params["phone"].blank?
+ user_data_hsh[:phone_numbers] = [{:phone_number => user_params["phone"]}]
+ end
+ unless user_params["sms"].blank?
+ user_data_hsh[:phone_numbers] ||= []
+ user_data_hsh[:phone_numbers] << {:phone_number => user_params["sms"]}
+ end
+
+ #add the demographic information
+ demographics_hash = get_demographic_information(user_data_hsh[:api_id])
+ user_data_hsh.merge!(demographics_hash) unless demographics_hash.blank?
+
+ #if you're a student, build the contacts too
+ if bright_type == "Student" and !user_params["agents"].blank?
+ user_data_hsh[:contacts] = user_params["agents"].collect do |agent_hsh|
+ begin
+ self.get_contact_by_api_id(agent_hsh["sourcedId"])
+ rescue Bright::ResponseError => e
+ if !e.message.to_s.include?("404")
+ raise e
+ end
+ end
+ end.compact
+ user_data_hsh[:grade] = (user_params["grades"] || []).first
+ if !user_data_hsh[:grade].blank?
+ user_data_hsh[:grade_school_year] = get_grade_school_year
+ end
+ end
+
+ return user_data_hsh
+ end
+
+ def get_demographic_information(api_id)
+ demographic_hsh = {}
+
+ begin
+ demographics_params = request(:get, "demographics/#{api_id}")["demographics"]
+ rescue Bright::ResponseError => e
+ if e.message.to_s.include?('404')
+ return demographic_hsh
+ else
+ raise e
+ end
+ end
+
+ unless (bday = demographics_params["birthdate"] || demographics_params["birthDate"]).blank?
+ demographic_hsh[:birth_date] = Date.parse(bday).to_s
+ end
+ unless demographics_params["sex"].to_s[0].blank?
+ demographic_hsh[:gender] = demographics_params["sex"].to_s[0].upcase
+ end
DEMOGRAPHICS_CONVERSION.each do |demographics_key, demographics_value|
- if demographics_params[demographics_key] == "true"
- student_data_hsh[:race] ||= []
- student_data_hsh[:race] << demographics_value
+ if demographics_params[demographics_key].to_bool
+ if demographics_value == "Hispanic Or Latino"
+ demographic_hsh[:hispanic_ethnicity] = true
+ else
+ demographic_hsh[:race] ||= []
+ demographic_hsh[:race] << demographics_value
+ end
end
end
- return student_data_hsh
+ return demographic_hsh
+ end
+
+ def get_grade_school_year(date = Date.today)
+ #return the school year of a specific date
+ self.school_years_cache ||= {}
+ if self.school_years_cache[date].nil?
+ academic_periods_params = self.request(:get, "academicSessions", {"filter" => "startDate<='#{date.to_s}' AND endDate>='#{date.to_s}' AND status='active'"})["academicSessions"]
+ school_years = academic_periods_params.map{|ap| ap["schoolYear"]}.uniq
+ if school_years.size == 1
+ self.school_years_cache[date] = school_years.first
+ end
+ end
+ return self.school_years_cache[date]
end
end
end
end