bright 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,248 @@
1
+ require 'base64'
2
+ require 'json'
3
+
4
+ module Bright
5
+ module SisApi
6
+ class TSIS < Base
7
+ DATE_FORMAT = '%m/%d/%Y'
8
+
9
+ @@description = "Connects to the TIES API for accessing TIES TSIS student information"
10
+ @@doc_url = "#unkown"
11
+
12
+ attr_accessor :connection_options
13
+
14
+ def initialize(options = {})
15
+ self.connection_options = options[:connection] || {}
16
+ # {
17
+ # :key => "",
18
+ # :secret => "",
19
+ # :district_id => "",
20
+ # :uri => ""
21
+ # }
22
+ end
23
+
24
+ def get_student_by_api_id(api_id, params = {})
25
+ self.get_student(params.merge({:sis_student_id => api_id}))
26
+ end
27
+
28
+ def get_student(params = {}, options = {})
29
+ params = self.apply_options(params, options.merge({:per_page => 1}))
30
+
31
+ # Students only gets you students that are enrolled in a school for a given school year.
32
+ students_response_hash = self.request(:get, 'Students/', self.map_search_params(params))
33
+ found_student = nil
34
+ if students_response_hash["Return"] and students_response_hash["Return"].first
35
+ found_student = Student.new(convert_to_student_data(students_response_hash["Return"].first))
36
+ end
37
+ if found_student.nil?
38
+ # Students/Family can get you students that are not enrolled in a school for a given school year.
39
+ family_response_hash = self.request(:get, 'Students/Family/', self.map_search_params(params))
40
+ if family_response_hash["Return"] and family_response_hash["Return"].first
41
+ found_student = Student.new(convert_to_student_data(family_response_hash["Return"].first))
42
+ end
43
+ end
44
+ found_student
45
+ end
46
+
47
+ def get_students(params = {}, options = {})
48
+ params = self.apply_options(params, options)
49
+
50
+ if params[:schoolyear]
51
+ # Students only gets you students that are enrolled in a school for a given school year.
52
+ response_hash = self.request(:get, self.apply_page_to_url('Students/', options[:page]), self.map_search_params(params))
53
+ else
54
+ # Students/Family can get you students that are not enrolled in a school for a given school year.
55
+ response_hash = self.request(:get, self.apply_page_to_url('Students/Family/', options[:page]), self.map_search_params(params))
56
+ end
57
+
58
+ students = response_hash["Return"].collect{|hsh| Student.new(convert_to_student_data(hsh))}
59
+ total_results = response_hash["TotalCount"].to_i
60
+
61
+ if options[:wrap_in_collection] != false
62
+ api = self
63
+ load_more_call = proc { |page|
64
+ # pages start at one, so add a page here
65
+ api.get_students(params, {:wrap_in_collection => false, :page => (page + 1)})
66
+ }
67
+
68
+ ResponseCollection.new({
69
+ :seed_page => students,
70
+ :total => total_results,
71
+ :per_page => options[:per_page],
72
+ :load_more_call => load_more_call
73
+ })
74
+ else
75
+ students
76
+ end
77
+ end
78
+
79
+ def create_student(student, additional_params = {})
80
+ raise NotImplementedError, "TSIS does not support creating students"
81
+ end
82
+
83
+ def update_student(student, additional_params = {})
84
+ raise NotImplementedError, "TSIS does not support updating students"
85
+ end
86
+
87
+ def get_schools(params = {}, options = {})
88
+ params = self.apply_options(params, options)
89
+
90
+ # schools api end point takes page # in the url itself for some reason
91
+ schools_response_hash = self.request(:get, self.apply_page_to_url('Schools/', options[:page]), params)
92
+
93
+ schools = schools_response_hash["Return"].collect{|hsh| School.new(convert_to_school_data(hsh))}
94
+ total_results = schools_response_hash["TotalCount"].to_i
95
+
96
+ if options[:wrap_in_collection] != false
97
+ api = self
98
+ load_more_call = proc { |page|
99
+ # pages start at one, so add a page here
100
+ api.get_schools(params, {:wrap_in_collection => false, :page => (page + 1)})
101
+ }
102
+
103
+ ResponseCollection.new({
104
+ :seed_page => schools,
105
+ :total => total_results,
106
+ :per_page => options[:per_page],
107
+ :load_more_call => load_more_call
108
+ })
109
+ else
110
+ schools
111
+ end
112
+ end
113
+
114
+ def request(method, path, params = {})
115
+ uri = "#{self.connection_options[:uri]}/#{path}"
116
+ body = nil
117
+ query = URI.encode(params.map{|k,v| "#{k}=#{v}"}.join("&"))
118
+ if method == :get
119
+ uri += "?#{query}"
120
+ else
121
+ body = query
122
+ end
123
+
124
+ headers = self.headers_for_auth(uri)
125
+
126
+ connection = Bright::Connection.new(uri)
127
+ response = connection.request(method, body, headers)
128
+ if !response.error?
129
+ response_hash = JSON.parse(response.body)
130
+ end
131
+ response_hash
132
+ end
133
+
134
+ protected
135
+
136
+ def map_search_params(params)
137
+ params = params.dup
138
+
139
+ params["studentname"] = params.delete(:name)
140
+ params["studentname"] ||= "#{params.delete(:last_name)}, #{params.delete(:first_name)} #{params.delete(:middle_name)}".strip
141
+ params["studentids"] = params.delete(:sis_student_id)
142
+
143
+ params = Hash[params.collect do |k,v|
144
+ if v.is_a?(Array)
145
+ v = v.join(",")
146
+ end
147
+ k = k.to_s.gsub(/[^A-Za-z]/, "").downcase
148
+ [k,v]
149
+ end]
150
+
151
+ params.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
152
+ end
153
+
154
+ def convert_to_student_data(attrs)
155
+ catt = {}
156
+ if attrs["StudentName"]
157
+ split_name = attrs["StudentName"].strip.split(",")
158
+ if split_name[1]
159
+ split_first_name = split_name[1].to_s.strip.split(" ")
160
+ if split_first_name.size > 1
161
+ catt[:first_name] = split_first_name[0...-1].join(" ").strip
162
+ catt[:middle_name] = split_first_name[-1].strip
163
+ else
164
+ catt[:first_name] = split_first_name.first.strip
165
+ end
166
+ end
167
+ catt[:last_name] = split_name[0].to_s.strip
168
+ else
169
+ catt[:first_name] = attrs["FirstName"].strip
170
+ catt[:middle_name] = attrs["MiddleName"].strip
171
+ catt[:last_name] = (attrs["LastName"] || attrs["SurName"]).strip
172
+ end
173
+
174
+ catt[:state_student_id] = (attrs["StateId"] || attrs["StudentStateId"]).to_s
175
+ catt[:sis_student_id] = attrs["StudentId"].to_s
176
+ catt[:api_id] = attrs["StudentId"].to_s
177
+ catt[:homeless_code] = attrs["HomelessCode"]
178
+
179
+ # "Economic\":{\"Description\":\"\",\"EconomicIndicatorId\":\"0\"}
180
+
181
+ bd = attrs["BirthDate"] || attrs["StudentBirthDate"]
182
+ if !(bd.nil? or bd.empty?)
183
+ begin
184
+ catt[:birth_date] = Date.strptime(bd, DATE_FORMAT)
185
+ rescue => e
186
+ puts "#{e.inspect} #{bd}"
187
+ end
188
+ end
189
+
190
+ catt[:addresses] = [self.convert_to_address_data(attrs["Address"])] if attrs["Address"]
191
+
192
+ catt.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
193
+ end
194
+
195
+ def convert_to_address_data(attrs)
196
+ cattrs = {}
197
+
198
+ if attrs["Line1"]
199
+ cattrs[:street] = "#{attrs["Line1"]}\n#{attrs["Line2"]}\n#{attrs["Line3"]}".strip
200
+ elsif attrs["Address1"]
201
+ cattrs[:street] = "#{attrs["Address1"]}\n#{attrs["Address2"]}".strip
202
+ end
203
+ cattrs[:city] = attrs["City"]
204
+ cattrs[:state] = attrs["State"]
205
+ cattrs[:postal_code] = attrs["Zip"]
206
+ cattrs[:type] = attrs["Type"].to_s.downcase
207
+
208
+ cattrs.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
209
+ end
210
+
211
+ def convert_to_school_data(attrs)
212
+ cattrs = {}
213
+
214
+ cattrs[:name] = attrs["Name"]
215
+ cattrs[:api_id] = cattrs[:number] = attrs["SchoolId"]
216
+ cattrs[:address] = convert_to_address_data(attrs)
217
+
218
+ cattrs.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
219
+ end
220
+
221
+ def apply_options(params, options)
222
+ options[:per_page] = params[:rpp] ||= params.delete(:per_page) || options[:per_page] || 100
223
+ options[:page] = params.delete(:page) || options[:page]
224
+ params[:schoolyear] ||= params.delete(:school_year) || options[:school_year]
225
+ params
226
+ end
227
+
228
+ def apply_page_to_url(url, page)
229
+ "#{url}#{url[-1] == "/" ? "" : "/"}#{page}"
230
+ end
231
+
232
+ def headers_for_auth(uri)
233
+ t = Time.now.utc.httpdate
234
+ string_to_sign = "GET\n#{t}\n#{uri}"
235
+
236
+ signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), self.connection_options[:secret], string_to_sign)).strip
237
+ authorization = "TIES" + " " + self.connection_options[:key] + ":" + signature
238
+
239
+ {
240
+ 'Authorization' => authorization,
241
+ 'DistrictNumber' => self.connection_options[:district_id],
242
+ 'ties-date' => t,
243
+ 'Content-Type' => "application/json"
244
+ }
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,53 @@
1
+ require 'securerandom'
2
+
3
+ module Bright
4
+ class Student < Model
5
+ @attribute_names = [:client_id, :api_id, :first_name, :middle_name, :last_name, :nick_name,
6
+ :birth_date, :grade, :projected_graduation_year, :gender,
7
+ :hispanic_ethnicity, :race, :image, :primary_language, :secondary_language,
8
+ :homeless_code, :frl_status, :sis_student_id,
9
+ :state_student_id, :last_modified]
10
+ attr_accessor *@attribute_names
11
+
12
+ def self.attribute_names
13
+ @attribute_names
14
+ end
15
+
16
+ # TODO:: map contact info (addresses, email, phone, etc)
17
+ attr_accessor :enrollment, :addresses
18
+
19
+ def initialize(*args)
20
+ super
21
+ self.client_id ||= SecureRandom.uuid
22
+ self
23
+ end
24
+
25
+ def name
26
+ "#{self.first_name} #{self.middle_name} #{self.last_name}".gsub(/\s+/, " ").strip
27
+ end
28
+
29
+ def <=>(other)
30
+ (self.sis_student_id and self.sis_student_id == other.sis_student_id) or
31
+ (self.state_student_id and self.state_student_id == other.state_student_id) or
32
+ (self.first_name == other.first_name and self.middle_name == other.middle_name and self.last_name == other.last_name and self.birth_date == other.birth_date)
33
+ end
34
+
35
+ alias id client_id
36
+
37
+ def addresses=(array)
38
+ if array.size <= 0 or array.first.is_a?(Address)
39
+ @addresses = array
40
+ @addresses.each{|a| a.student = self}
41
+ elsif array.first.is_a?(Hash)
42
+ @addresses = array.collect{|a| Address.new(a)}
43
+ end
44
+ @addresses ||= []
45
+ end
46
+
47
+ def addresses
48
+ @addresses ||= []
49
+ end
50
+ end
51
+ end
52
+
53
+
@@ -0,0 +1,3 @@
1
+ module Bright
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bright
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Arux Software
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httpi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ description: Bright is a simple Student Information System API abstraction library
70
+ used in and sponsored by FeePay. It is written by Stephen Heuer, Steven Novotny,
71
+ and contributors. The aim of the project is to abstract as many parts as possible
72
+ away from the user to offer a consistent interface across all supported Student
73
+ Information System APIs.
74
+ email:
75
+ - sheuer@aruxsoftware.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - ".gitignore"
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - bright.gemspec
86
+ - lib/bright.rb
87
+ - lib/bright/address.rb
88
+ - lib/bright/connection.rb
89
+ - lib/bright/enrollment.rb
90
+ - lib/bright/errors.rb
91
+ - lib/bright/model.rb
92
+ - lib/bright/response_collection.rb
93
+ - lib/bright/school.rb
94
+ - lib/bright/sis_apis/aeries.rb
95
+ - lib/bright/sis_apis/base.rb
96
+ - lib/bright/sis_apis/infinite_campus.rb
97
+ - lib/bright/sis_apis/power_school.rb
98
+ - lib/bright/sis_apis/synergy.rb
99
+ - lib/bright/sis_apis/tsis.rb
100
+ - lib/bright/student.rb
101
+ - lib/bright/version.rb
102
+ homepage: ''
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.2.2
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Framework and tools for dealing with Student Information Systems
126
+ test_files: []