ps_utilities 0.3.2 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4abd4b17bf893e811f7e920887cc881baad21ffc7b43e112de022ac5240269e2
4
- data.tar.gz: da43f677c4b59e491e352894061a96290406dea47ad6c6bb0f395715ebead669
3
+ metadata.gz: 30083f1244c4e8ea1a53ab81b8f88ac3c10d177fb651218cfcbe9dca8bdc219f
4
+ data.tar.gz: c7f3991b61f85c224c48ca2bb3c172b8a2b24b50d28a8e14e1b80e9e5f831c4d
5
5
  SHA512:
6
- metadata.gz: 299bbefec900eef22d92c605838e90358f6cae6a561828ba2229d813e0e5fcdccc0df95d145c13e2e40d6b7b0b03d79e54f9c03e531ddfb90c5911a6622f1382
7
- data.tar.gz: edb76484fc23806fd56b1dd67573a8e2466513023340535f79437751c808423bb0a30025a182ba4141221eb8483efc0b2f5a22d80f1d08fed2faec0f0c4ae238
6
+ metadata.gz: 848cfcbb6cf7b71d1e205ac04c21ac14a7b75be92b47db43effa58d6d455ac5b20a90e0d022070e0f9c0e4e1925599fca75493ec5a8f30047205b13a758fbae3
7
+ data.tar.gz: f72b28802331533bb855536a2dcbf1ece95bab2fc3a4d09b7aeca883b4a1003c9d3ef1d2d403dd86d55b64ff5e641467f84125b86766370f8e10de41cdda0f66
@@ -24,30 +24,32 @@ module PsUtilities
24
24
 
25
25
  class Connection
26
26
 
27
- attr_reader :credentials, :headers, :base_uri
28
- attr_reader :auth_path, :auth_token
27
+ attr_reader :auth_path, :auth_token, :auth_info, :headers
28
+ attr_reader :api_data, :base_uri, :auth_path
29
+ attr_reader :client, :client_id, :client_secret
29
30
  attr_reader :version
30
31
 
31
32
  include PsUtilities::PreBuiltGet
32
33
  include PsUtilities::PreBuiltPut
33
34
  include PsUtilities::PreBuiltPost
34
35
 
35
- # @param attributes: [Hash] - options include: { base_uri: ENV['PS_URL'], auth_endpoint: (ENV['PS_AUTH_ENDPOINT'] || '/oauth/access_token'), client_id: ENV['PS_CLIENT_ID'], client_secret: ENV['PS_CLIENT_SECRET'] }
36
+ # @param attributes: [Hash] - options include: { base_uri: ENV['PS_BASE_URL'], auth_endpoint: (ENV['PS_AUTH_ENDPOINT'] || '/oauth/access_token'), client_id: ENV['PS_CLIENT_ID'], client_secret: ENV['PS_CLIENT_SECRET'] }
36
37
  # @param headers: [Hash] - allows to change from json to xml (only do this if you are doing direct api calls and not using pre-built calls) returns and use a different useragent: { 'User-Agent' => "PsUtilities - #{version}", 'Accept' => 'application/json', 'Content-Type' => 'application/json'}
37
38
  # @note preference is to use environment variables to initialize your server.
38
- def initialize(attributes: {}, headers: {})
39
- @version = "v#{PsUtilities::Version::VERSION}"
40
- @credentials = attr_defaults.merge(attributes)
41
- @base_uri = credentials[:base_uri]
42
- @auth_path = credentials[:auth_endpoint]
43
- @headers = header_defaults.merge(headers)
44
-
45
- raise ArgumentError, "missing client_secret" if credentials[:client_secret].nil? or
46
- credentials[:client_secret].empty?
47
- raise ArgumentError, "missing client_id" if credentials[:client_id].nil? or
48
- credentials[:client_id].empty?
49
- raise ArgumentError, "missing base_uri" if credentials[:base_uri].nil? or
50
- credentials[:base_uri].empty?
39
+ def initialize( header_info: {}, api_info: {}, client_info: {})
40
+ @version = "v#{PsUtilities::Version::VERSION}"
41
+ @client = client_defaults.merge(client_info)
42
+ @client_id = client[:client_id]
43
+ @client_secret = client[:client_secret]
44
+ @api_data = api_defaults.merge(api_info)
45
+ @base_uri = api_data[:base_uri]
46
+ @auth_path = api_data[:auth_endpoint]
47
+ @headers = header_defaults.merge(header_info)
48
+
49
+ raise ArgumentError, "missing client_secret" if client_secret.nil? or client_secret.empty?
50
+ raise ArgumentError, "missing client_id" if client_id.nil? or client_id.empty?
51
+ raise ArgumentError, "missing auth endpoint" if auth_path.nil? or auth_path.empty?
52
+ raise ArgumentError, "missing base_uri" if base_uri.nil? or base_uri.empty?
51
53
  end
52
54
 
53
55
  # this runs the various options:
@@ -57,12 +59,14 @@ module PsUtilities
57
59
  # @param params: [Hash] - this is the data needed for using pre-built commands - see the individual command for details
58
60
  # @note with no command an authenticatation check is done
59
61
  def run(command: nil, api_path: "", options: {}, params: {})
60
- authenticate unless token_valid?
61
- @headers[:headers].merge!('Authorization' => 'Bearer ' + authorized_token)
62
+ authenticate unless auth_valid?
63
+
62
64
  case command
63
65
  when nil, :authenticate
66
+ authenticate
64
67
  when :delete, :get, :patch, :post, :put
65
- api(command, api_path, options) unless api_path.empty?
68
+ api(command, api_path, options) unless api_path.empty?
69
+ # TODO: panick if api_path empty
66
70
  else
67
71
  send(command, params)
68
72
  end
@@ -71,7 +75,7 @@ module PsUtilities
71
75
  private
72
76
 
73
77
  def authorized_token
74
- "#{credentials[:access_token]}"
78
+ "#{auth_info['access_token']}"
75
79
  end
76
80
 
77
81
  # verb = :delete, :get, :patch, :post, :put
@@ -81,10 +85,6 @@ module PsUtilities
81
85
  retries = 3
82
86
  ps_url = base_uri + api_path
83
87
  options = options.merge(headers)
84
- pp "api-url"
85
- pp ps_url
86
- pp "api-options"
87
- pp options
88
88
  begin
89
89
  HTTParty.send(verb, ps_url, options)
90
90
  rescue Net::ReadTimeout, Net::OpenTimeout
@@ -101,7 +101,8 @@ module PsUtilities
101
101
  { headers:
102
102
  { 'User-Agent' => "PsUtilities - #{version}",
103
103
  'Accept' => 'application/json',
104
- 'Content-Type' => 'application/json'}
104
+ 'Content-Type' => 'application/json'
105
+ }
105
106
  }
106
107
  end
107
108
 
@@ -110,46 +111,52 @@ module PsUtilities
110
111
  ps_url = base_uri + auth_path
111
112
  response = HTTParty.post(ps_url, {headers: auth_headers,
112
113
  body: 'grant_type=client_credentials'})
113
-
114
- @credentials[:token_expires] = Time.now + response.parsed_response['expires_in'].to_i
115
- @credentials[:access_token] = response.parsed_response['access_token'].to_s
116
- # @headers[:headers].merge!('Authorization' => 'Bearer ' + credentials[:access_token])
117
-
118
- # throw error if no token returned -- nothing else will work
119
- raise AuthError.new("No Auth Token Returned",
120
- ps_url, credentials
121
- ) if credentials[:access_token].empty?
114
+ if response.code.to_s.eql? "200"
115
+ @auth_info = response.parsed_response
116
+ @auth_info['token_expires'] = Time.now + response.parsed_response['expires_in'].to_i
117
+ @headers[:headers].merge!('Authorization' => 'Bearer ' + auth_info['access_token'])
118
+ return auth_info
119
+ else
120
+ # throw error if - error returned -- nothing else will work
121
+ raise AuthError.new("No Auth Token Returned", ps_url, client )
122
+ end
122
123
  end
123
124
 
124
- def token_valid?(tokens = credentials)
125
- return false if tokens[:access_token].nil?
126
- return false if tokens[:access_token].empty?
127
- return false if tokens[:token_expires].nil?
128
- return false if tokens[:token_expires] <= Time.now
125
+ def auth_valid?(auth = auth_info)
126
+ return false if auth.nil?
127
+ return false if auth.empty?
128
+ return false if auth['access_token'].nil?
129
+ return false if auth['access_token'].empty?
130
+ return false if auth['token_expires'].nil?
131
+ return false if auth['token_expires'] < Time.now
129
132
  return true
130
133
  end
131
134
 
132
- def auth_headers(creds64 = encode_credentials)
135
+ def auth_headers(credentials = client)
133
136
  { 'ContentType' => 'application/x-www-form-urlencoded;charset=UTF-8',
134
137
  'Accept' => 'application/json',
135
- 'Authorization' => 'Basic ' + creds64
138
+ 'Authorization' => 'Basic ' + encode64_client(credentials)
136
139
  }
140
+ # with(headers: {'Authorization' => "Basic #{ Base64.strict_encode64('user:pass').chomp}"})
137
141
  end
138
142
 
139
- def encode_credentials(creds = credentials)
140
- ps_auth_text = [ creds[:client_id],
141
- creds[:client_secret]
142
- ].join(':')
143
- Base64.encode64(ps_auth_text).gsub(/\n/, '')
143
+ def encode64_client(credentials = client)
144
+ ps_auth_text = [ credentials[:client_id], credentials[:client_secret] ].join(':')
145
+ Base64.encode64(ps_auth_text).chomp
146
+ # Base64.encode64(ps_auth_text).gsub(/\n/, '')
144
147
  end
145
148
 
146
- def attr_defaults
147
- { base_uri: ENV['PS_URL'],
148
- auth_endpoint: ENV['PS_AUTH_ENDPOINT'] || '/oauth/access_token',
149
- client_id: ENV['PS_CLIENT_ID'],
149
+ def client_defaults
150
+ { client_id: ENV['PS_CLIENT_ID'],
150
151
  client_secret: ENV['PS_CLIENT_SECRET'],
151
152
  }
152
153
  end
153
154
 
155
+ def api_defaults
156
+ { base_uri: ENV['PS_BASE_URL'],
157
+ auth_endpoint: ENV['PS_AUTH_ENDPOINT'] || '/oauth/access_token',
158
+ }
159
+ end
160
+
154
161
  end
155
162
  end
@@ -3,11 +3,11 @@ module PsUtilities
3
3
  module PreBuiltGet
4
4
 
5
5
  # return all active students within the district (special case of #get_all_matching_students) - a recursive search
6
- # @param params [Hash] - ignored - only included for the api standard
6
+ # @param params [Hash] - page_size: is the only parameter accepted - default is 100
7
7
  # @return - (see #get_all_matching_students)
8
8
  def get_all_active_students(params={})
9
- params = {status_code: 0}
10
- # params[:status_code] = 0
9
+ page_size = params[:page_size] || 100
10
+ params = {status_code: 0, page_size: page_size}
11
11
  get_all_matching_students(params)
12
12
  end
13
13
  alias_method :all_active_students, :get_all_active_students
@@ -117,12 +117,32 @@ module PsUtilities
117
117
  return {"errorMessage"=>{"message"=>"A valid dcid must be entered."}} if "#{ps_dcid.to_i}".eql? "0"
118
118
 
119
119
  answer = api(:get, api_path, options)
120
- { student: (answer["student"] || []) }
120
+ return { student: (answer["student"] || []) } if answer.code.to_s.eql? "200"
121
+ # return { student: (answer.parsed_response["student"] || []) } if answer.code.to_s.eql? "200"
122
+ return {"errorMessage"=>"#{answer.response}"}
121
123
  end
122
124
  alias_method :get_student, :get_one_student
123
125
 
124
126
  private
125
127
 
128
+ # build the api query - you can use splats to match any character
129
+ # @param params [Hash] - valid keys include: :status_code (or :enroll_status), :username, :last_name, :first_name, :student_id (or :local_id), :id (or :dcid)
130
+ # @return [String] - "id==345;name.last_name==BA*"
131
+ def build_query(params)
132
+ query = []
133
+ query << "school_enrollment.enroll_status_code==#{params[:status_code]}" if params.has_key?(:status_code)
134
+ query << "school_enrollment.enroll_status==#{params[:enroll_status]}" if params.has_key?(:enroll_status)
135
+ query << "student_username==#{params[:username]}" if params.has_key?(:username)
136
+ query << "name.last_name==#{params[:last_name]}" if params.has_key?(:last_name)
137
+ query << "name.first_name==#{params[:first_name]}" if params.has_key?(:first_name)
138
+ query << "local_id==#{params[:local_id]}" if params.has_key?(:local_id)
139
+ query << "local_id==#{params[:student_id]}" if params.has_key?(:student_id)
140
+ query << "id==#{params[:dcid]}" if params.has_key?(:dcid)
141
+ query << "id==#{params[:id]}" if params.has_key?(:id)
142
+ answer = query.join(";")
143
+ answer
144
+ end
145
+
126
146
  # given the number of students and page size calculate pages needed to return all students
127
147
  # @param count [Integer] - total number of students matching filter
128
148
  # @param page_size [Integer] - total number of students to be return per page
@@ -185,25 +205,9 @@ module PsUtilities
185
205
  options[:query]["q"] = query unless query.empty?
186
206
  return {"errorMessage"=>{"message"=>"A valid parameter must be entered."}} if query.empty?
187
207
  # pp options
188
- api(:get, api_path, options)
189
- end
190
-
191
- # build the api query - you can use splats to match any character
192
- # @param params [Hash] - valid keys include: :status_code (or :enroll_status), :username, :last_name, :first_name, :student_id (or :local_id), :id (or :dcid)
193
- # @return [String] - "id==345;name.last_name==BA*"
194
- def build_query(params)
195
- query = []
196
- query << "school_enrollment.enroll_status_code==#{params[:status_code]}" if params.has_key?(:status_code)
197
- query << "school_enrollment.enroll_status==#{params[:enroll_status]}" if params.has_key?(:enroll_status)
198
- query << "student_username==#{params[:username]}" if params.has_key?(:username)
199
- query << "name.last_name==#{params[:last_name]}" if params.has_key?(:last_name)
200
- query << "name.first_name==#{params[:first_name]}" if params.has_key?(:first_name)
201
- query << "local_id==#{params[:local_id]}" if params.has_key?(:local_id)
202
- query << "local_id==#{params[:student_id]}" if params.has_key?(:student_id)
203
- query << "id==#{params[:dcid]}" if params.has_key?(:dcid)
204
- query << "id==#{params[:id]}" if params.has_key?(:id)
205
- answer = query.join(";")
206
- answer
208
+ answer = api(:get, api_path, options)
209
+ return answer.parsed_response if answer.code.to_s.eql? "200"
210
+ return {"errorMessage"=>"#{answer.response}"}
207
211
  end
208
212
 
209
213
  end
@@ -30,28 +30,36 @@ module PsUtilities
30
30
  # ]
31
31
  # }
32
32
  # }
33
+ # @note create_students REQUIRED params are: :id
34
+ # @note create_students OPTIONAL params are:
35
+ # @note create_students INVALID params are:
33
36
  def create_students(params)
34
37
  action = "INSERT"
35
38
  kids_api_array = build_kids_api_array(action, params)
36
39
  options = { body: { students: { student: kids_api_array } }.to_json }
37
40
  answer = api(:post, "/ws/v1/student", options)
41
+ return answer.parsed_response if answer.code.to_s.eql? "200"
42
+ return {"errorMessage"=>"#{answer.response}"}
38
43
  end
39
44
  alias_method :create_student, :create_students
40
45
 
41
46
  # this updates and existing student record within PowerSchool
42
47
  # (see #create_students)
48
+ # @note update_students REQUIRED params are: :last_name, :first_name, :entry_date, :exit_date, :school_number, :grade_level
49
+ # @note update_students OPTIONAL params are: :
50
+ # @note update_students INVALID params are:
43
51
  def update_students(params)
44
- pp "update students"
45
- pp params
46
52
  action = "UPDATE"
47
53
  kids_api_array = build_kids_api_array(action, params)
48
- pp kids_api_array
49
54
  options = { body: { students: { student: kids_api_array } }.to_json }
50
- pp options
51
55
  answer = api(:post, "/ws/v1/student", options)
56
+ return answer.parsed_response if answer.code.to_s.eql? "200"
57
+ return {"errorMessage"=>"#{answer.response}"}
52
58
  end
53
59
  alias_method :update_student, :update_students
54
60
 
61
+ private
62
+
55
63
  # @param action [String] - either "INSERT" or "UPDATE"
56
64
  # @param params [Array of Hashes] - in this format -- students: [{kid_1_info}, {kid_2_info}]
57
65
  # @return [Array of Hashes] - with data like below:
@@ -85,14 +93,12 @@ module PsUtilities
85
93
  #]
86
94
  # @note this is then sent to the API call with a body tag
87
95
  def build_kids_api_array(action, params)
88
- pp "build_kids_api_array"
89
- pp params
90
96
  students = []
91
97
  api_array = []
92
- students << params[:student] if params[:student]
93
- students = params[:students] if params[:students]
94
- unless students.is_a? Array
95
- return {"errorMessage"=>{"message"=>"Student Data (in Hash format) must be in an Array."}}
98
+ # students = [params[:student]] if not params[:student].nil? && params[:student].is_a?(Hash)
99
+ students = params[:students] #if not params[:students].nil? && params[:students].is_a?(Array)
100
+ if students.empty?
101
+ return {"errorMessage"=>{"message"=>"Bad student data - USE: {students: [{kid_data1}, {kid_data2}]}"}}
96
102
  end
97
103
  students.each do |kid|
98
104
  # kid[:las_extensions] = true if params[:las_extensions]
@@ -174,13 +180,10 @@ module PsUtilities
174
180
  # ]
175
181
  # }
176
182
  def build_kid_attributes(action, kid)
177
- pp "build_kid_attributes"
178
- pp kid
179
183
  # ALWAYS NEEDED INFO
180
184
  attribs = {action: action}
181
- attribs[:id] = kid[:id] || kid[:dcid]
182
185
  attribs[:client_uid] = kid[:student_id].to_s
183
- attribs[:student_username] = kid[:username]
186
+ attribs[:student_username] = kid[:username] if kid[:username]
184
187
 
185
188
  # REQUIRED ON ENROLLMENT (optional later)
186
189
  attribs[:name] = {}
@@ -206,6 +209,7 @@ module PsUtilities
206
209
  attribs[:school_enrollment][:school_id] = kid[:school_id] if kid[:school_id]
207
210
  when 'UPDATE'
208
211
  # don't allow nil / blank name updates
212
+ attribs[:id] = kid[:id] || kid[:dcid]
209
213
  attribs[:name][:last_name] = kid[:last_name] if kid[:last_name]
210
214
  attribs[:name][:first_name] = kid[:first_name] if kid[:first_name]
211
215
  attribs[:name][:middle_name] = kid[:middle_name] if kid[:middle_name]
@@ -278,9 +282,9 @@ module PsUtilities
278
282
  # Update LAS Database Extensions as needed
279
283
  attribs["_extension_data"] = { "_table_extension" => [] }
280
284
  # built-in extensions by PowerSchool
281
- attribs["_extension_data"]["_table_extension"] << u_studentsuserfields(kid[:u_studentsuserfields])
285
+ attribs["_extension_data"]["_table_extension"] << u_studentsuserfields(kid[:u_studentsuserfields]) if kid[:u_studentsuserfields]
282
286
  # school defined database extensions
283
- attribs["_extension_data"]["_table_extension"] << u_students_extension(kid[:u_students_extension])
287
+ attribs["_extension_data"]["_table_extension"] << u_students_extension(kid[:u_students_extension]) if kid[:u_students_extension]
284
288
  # if no extension data present make it empty
285
289
  attribs["_extension_data"] = {} if attribs["_extension_data"]["_table_extension"].empty?
286
290
 
@@ -1,5 +1,5 @@
1
1
  module PsUtilities
2
2
  module Version
3
- VERSION = "0.3.2"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ps_utilities
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Weisbecker
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2018-06-27 00:00:00.000000000 Z
12
+ date: 2018-06-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty
@@ -67,6 +67,20 @@ dependencies:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
69
  version: '3.7'
70
+ - !ruby/object:Gem::Dependency
71
+ name: webmock
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '3.4'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3.4'
70
84
  description: 'Uses oauth2 to connection to the Powerschool API. Heavily refactored
71
85
  code (not dependent on Rails) starting with: https://github.com/TomK32/powerschool'
72
86
  email: