bright 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.
@@ -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: []