bridge_api 0.1.63 → 0.1.68

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: f7d6ea6a260943cb99e83ffdd26cf7c0e68d0d383ca31e7cd96a16065261903a
4
- data.tar.gz: a8e36b9a696de1d2245dcb8bea4844a21fac2b6da185032b07101d424e66143c
3
+ metadata.gz: 2e33cd3207fe1494cbc64ea799f77ee399081ad6282283aaa5d577ecbe2b91c1
4
+ data.tar.gz: f2dea18b065d73fa9fe87598d236af598daa85d90dd884ee4b815c9f818b44d5
5
5
  SHA512:
6
- metadata.gz: c9fb10e6dc27d66c2ba3273ebfd7b0770245590256d1782be1cf9398b3a61c4ef36e2af7677da48322cabc35d0ca932a2ab3828f650b523e0ba85f71e6b3db14
7
- data.tar.gz: a828d6bef4d11150bd2b4968fdeb81908c06e44de3ab8fb044fff5bc09c61b586d15c466b12ca41496bb5e3d1d5990ab12464691d6b6876327b7094451fd58e8
6
+ metadata.gz: 89c9a1618bba16b9a707de5ca0aa056a0fc7aad2313f5304f5ba49a231be58ae89e54cdfe66e799b8f36e400f89bacac1b9b71c49fa3d40655059ebf91f8d986
7
+ data.tar.gz: 59c0dc141d8693cd88eb89f9dc1dcf9993beb549548afa4048781bc631c1a93e0b08b94cb9b79197ea0bc413f86b7d4177ab5fa230a6ffd5fb77fa2304429823
@@ -0,0 +1,31 @@
1
+ # Virtual environments with possible versions infos: https://github.com/actions/virtual-environments
2
+
3
+ name: CI
4
+ on: [push]
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2 # Step 1: github action that clones the repository
10
+ - name: Setup Ruby # Step 2 (we gave the name 'Setup Ruby' to this step)
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.5.0
14
+ - name: Install bundle and gems # Step 4
15
+ run: |
16
+ gem install bundler
17
+ bundle install --jobs 4 --retry 3
18
+ - name: Run tests # Step 6
19
+ run: COVERAGE=true bundle exec rspec
20
+ brakeman:
21
+ name: runner / brakeman # Step 8
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - name: Check out code
25
+ uses: actions/checkout@v1
26
+ - name: brakeman
27
+ uses: reviewdog/action-brakeman@v1
28
+ with:
29
+ brakeman_version: 4.8.2
30
+ github_token: ${{ secrets.github_token }}
31
+ reporter: github-pr-review # Default is github-pr-check
data/.gitignore CHANGED
@@ -4,4 +4,6 @@
4
4
  bridge_api.iml
5
5
  .byebug.*
6
6
 
7
- .*.gem
7
+ .*.gem
8
+
9
+ .history
@@ -10,6 +10,18 @@ module BridgeAPI
10
10
  def update_group(group_id, params = {})
11
11
  put("#{API_PATH}#{AUTHOR_PATH}#{GROUPS_PATH}/#{group_id}", params)
12
12
  end
13
+
14
+ def group_relevance(params = {})
15
+ get("#{API_PATH}#{AUTHOR_PATH}/group_relevance", params)
16
+ end
17
+
18
+ def update_group_relevance(params = {})
19
+ put("#{API_PATH}#{AUTHOR_PATH}/group_relevance", params)
20
+ end
21
+
22
+ def delete_group_relevance(group_id, params)
23
+ delete("#{API_PATH}#{AUTHOR_PATH}/group_relevance/#{group_id}", params)
24
+ end
13
25
  end
14
26
  end
15
27
  end
@@ -10,6 +10,10 @@ module BridgeAPI
10
10
  def get_program(program_id, params = {})
11
11
  get("#{API_PATH}#{AUTHOR_PATH}#{PROGRAM_PATH}/#{program_id}", params)
12
12
  end
13
+
14
+ def complete_program(program_id, params = {})
15
+ post("#{API_PATH}#{ADMIN_PATH}#{PROGRAM_PATH}/#{program_id}/completions", params)
16
+ end
13
17
  end
14
18
  end
15
19
  end
@@ -62,13 +62,6 @@ module BridgeAPI
62
62
 
63
63
  # Override Footrest request for ApiArray support
64
64
  def request(method, &block)
65
- if has_token_pool?(config)
66
- (config[:api_tokens] || config[:api_keys].keys).size.times do
67
- break unless rate_limit_reached?
68
-
69
- rotate_token!
70
- end
71
- end
72
65
  enforce_rate_limits if rate_limit_reached?
73
66
  response = connection.send(method, &block)
74
67
  apply_rate_limits(response)
@@ -80,45 +73,47 @@ module BridgeAPI
80
73
  config[:api_tokens].is_a?(Array) && config[:api_tokens].count >= 1
81
74
  end
82
75
 
83
- # Since a pool is passed in, initialize first token with the first token passed in
84
- def initialize_from_token_pool(config)
85
- if config[:api_keys].is_a?(Hash)
86
- creds = config[:api_keys].first
87
- config[:api_key] ||= creds[0]
88
- config[:api_secret] ||= creds[1]
89
- elsif config[:api_tokens].is_a?(Array)
90
- config[:token] ||= config[:api_tokens].first
76
+ def convert_tokens(config)
77
+ return config unless config.has_key?(:api_tokens)
78
+ return config unless config[:api_tokens].is_a?(Array)
79
+ config[:api_keys] ||= {}
80
+ config[:api_tokens].each do |token|
81
+ decoded_token_array = Base64.strict_decode64(token).split(':')
82
+ config[:api_keys][decoded_token_array[0]] = decoded_token_array[1]
91
83
  end
84
+ config.delete(:api_tokens)
85
+ config
86
+ end
87
+
88
+ def initialize_from_token_pool(config)
89
+ config = convert_tokens(config)
90
+ creds = config[:api_keys].first
91
+ config[:api_key] ||= creds[0]
92
+ config[:api_secret] ||= creds[1]
92
93
  config
93
94
  end
94
95
 
95
96
  # rotates to the next token in the pool (by order in which they were provided)
96
- def rotate_token!
97
- BridgeAPI.master_mutex.synchronize do
98
- old_api_key = config[:api_key]
99
- if config[:api_keys].is_a?(Hash)
100
- keys = config[:api_keys].keys
101
- return if keys.count <= 1
102
-
103
- key = get_next_key(keys, config[:api_key])
104
- config[:api_key] = key
105
- config[:api_secret] = config[:api_keys][key]
106
- elsif config[:api_tokens].is_a?(Array)
107
- keys = config[:api_tokens]
108
- return if keys.count <= 1
109
-
110
- token = get_next_key(keys, config[:token])
111
- config[:token] = token
112
- end
113
- BridgeAPI.logger.debug('rotating API Keys')
114
- set_connection(config)
115
- end
97
+ def rotate_token!
98
+ return unless config[:api_keys].present?
99
+ old_api_key = config[:api_key]
100
+ keys = config[:api_keys].keys
101
+ return if keys.count <= 1
102
+ key = get_next_key(keys, config[:api_key])
103
+ config[:api_key] = key
104
+ config[:api_secret] = config[:api_keys][key]
105
+ set_connection(config)
106
+ BridgeAPI.logger.debug("ROTATED TO KEY: #{config[:api_key]}")
116
107
  end
117
108
 
118
109
  def get_next_key(keys, current_key)
119
- i = keys.index(current_key) || -1
120
- i = (i + 2) > keys.count ? 0 : (i + 1)
121
- keys[i]
110
+ keys.delete(current_key)
111
+ usable_key = keys.find do |key|
112
+ limit = rate_limit(key)
113
+ current_key_limit = limit.present? ? limit.fetch('current') : 0
114
+ BridgeAPI.beginning_rate_limit - current_key_limit > BridgeAPI.rate_limit_threshold
115
+ end
116
+ usable_key || keys[rand(keys.length)]
122
117
  end
123
118
 
124
119
  def rate_limit_reached?
@@ -130,14 +125,13 @@ module BridgeAPI
130
125
  def enforce_rate_limits
131
126
  return unless rate_limit_reached?
132
127
 
133
- tts = ((BridgeAPI.beginning_rate_limit - limit_remaining) / 5).ceil
134
- tts = BridgeAPI.min_sleep_seconds if tts < BridgeAPI.min_sleep_seconds
135
- tts = BridgeAPI.max_sleep_seconds if tts > BridgeAPI.max_sleep_seconds
136
- message = "Bridge API rate limit minimum #{BridgeAPI.rate_limit_threshold} reached for key: '#{config[:api_key]}'. "\
137
- "Sleeping for #{tts} second(s) to catch up ~zzZZ~. "\
138
- "Limit Remaining: #{limit_remaining}"
139
- BridgeAPI.logger.debug(message)
140
- sleep(tts)
128
+ rotate_token!
129
+ if rate_limit_reached?
130
+ sleep_time = rand(BridgeAPI.max_sleep_seconds)
131
+ sleep_time = BridgeAPI.min_sleep_seconds if sleep_time < BridgeAPI.min_sleep_seconds
132
+ BridgeAPI.logger.debug("Rate limit reached sleeping for #{sleep_time}")
133
+ sleep(sleep_time)
134
+ end
141
135
  end
142
136
 
143
137
  def using_master_rate_limit?
@@ -148,20 +142,18 @@ module BridgeAPI
148
142
  limit = response.headers['x-rate-limit-remaining']
149
143
  return if limit.nil?
150
144
 
151
- BridgeAPI.logger.debug("BRIDGE RATE LIMIT REMAINING: #{limit}")
145
+ BridgeAPI.logger.debug("BRIDGE RATE LIMIT REMAINING: #{limit} for key #{config[:api_key]}")
152
146
  self.limit_remaining = limit.to_i
153
147
  end
154
148
 
155
149
  def limit_remaining
156
150
  if using_master_rate_limit?
157
- BridgeAPI.master_mutex.synchronize do
158
- limit = PaulWalker::RateLimit.get(config[:api_key], config[:api_key])
159
- if limit.nil?
160
- PaulWalker::RateLimit.add(config[:api_key], config[:api_key], 0, BridgeAPI.beginning_rate_limit)
161
- limit = { current: 0 }.with_indifferent_access
162
- end
163
- limit['current']
151
+ limit = rate_limit(config[:api_key])
152
+ if limit.nil?
153
+ set_rate_limit(config[:api_key], 0)
154
+ limit = { current: 0 }.with_indifferent_access
164
155
  end
156
+ limit['current']
165
157
  else
166
158
  BridgeAPI.rate_limits[config[:api_key]]
167
159
  end
@@ -169,12 +161,37 @@ module BridgeAPI
169
161
 
170
162
  def limit_remaining=(value)
171
163
  if using_master_rate_limit?
172
- BridgeAPI.master_mutex.synchronize do
173
- PaulWalker::RateLimit.add(config[:api_key], config[:api_key], value, BridgeAPI.beginning_rate_limit)
174
- end
164
+ set_rate_limit(config[:api_key], value)
175
165
  else
176
166
  BridgeAPI.rate_limits[config[:api_key]] = value
177
167
  end
168
+ refresh_stale_tokens
169
+ end
170
+
171
+ def set_rate_limit(key, limit)
172
+ BridgeAPI.master_mutex.synchronize do
173
+ PaulWalker::RateLimit.add(key, key, limit, BridgeAPI.beginning_rate_limit)
174
+ end
175
+ end
176
+
177
+ def rate_limit(key)
178
+ BridgeAPI.master_mutex.synchronize do
179
+ PaulWalker::RateLimit.get(key, key)
180
+ end
181
+ end
182
+
183
+ def refresh_stale_tokens
184
+ return unless using_master_rate_limit?
185
+ return unless config[:api_keys].present?
186
+ api_keys = config[:api_keys].keys
187
+ api_keys.delete(config[:api_key])
188
+ api_keys.each do |key|
189
+ limit = rate_limit(key)
190
+ if limit['timestamp'].present? && DateTime.parse(limit['timestamp']) < 1.minute.ago
191
+ BridgeAPI.logger.debug("Refreshing: #{key}")
192
+ set_rate_limit(key, 0)
193
+ end
194
+ end
178
195
  end
179
196
 
180
197
  def set_connection(config)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BridgeAPI
4
- VERSION = '0.1.63' unless defined?(BridgeAPI::VERSION)
4
+ VERSION = '0.1.68' unless defined?(BridgeAPI::VERSION)
5
5
  end
data/lib/bridge_api.rb CHANGED
@@ -26,7 +26,7 @@ module BridgeAPI
26
26
  end
27
27
 
28
28
  def max_sleep_seconds
29
- @max_sleep_seconds ||= 60
29
+ @max_sleep_seconds ||= 30
30
30
  end
31
31
 
32
32
  def master_rate_limit
@@ -27,12 +27,6 @@ describe BridgeAPI::Client::Enrollment do
27
27
  expect(enrollments.length).to(eq(2))
28
28
  end
29
29
 
30
- it 'should update due date for enrollments' do
31
- enrollment = [{ end_at: nil }]
32
- response = @client.update_enrollment_due_date(1, enrollment)
33
- expect(response.status).to(eq(200))
34
- end
35
-
36
30
  it 'should reset an enrollment for a course template' do
37
31
  response = @client.reset_enrollment(1)
38
32
  expect(response.status).to(eq(204))
@@ -52,7 +52,7 @@ describe BridgeAPI::Client do
52
52
  end
53
53
 
54
54
  it 'should return true for tokens' do
55
- config = { prefix: 'https://www.fake.com', api_tokens: %w[token1 token2] }
55
+ config = { prefix: 'https://www.fake.com', api_tokens: %w[dGVzdDE6dGVzdDE= dGVzdDI6dGVzdDI=] }
56
56
  client = BridgeAPI::Client.new(config)
57
57
  expect(client.has_token_pool?(config)).to be_truthy
58
58
  end
@@ -74,10 +74,10 @@ describe BridgeAPI::Client do
74
74
  end
75
75
 
76
76
  it 'should take first from tokens' do
77
- config = { prefix: 'https://www.fake.com', api_tokens: %w[token1 token2] }
77
+ config = { prefix: 'https://www.fake.com', api_tokens: %w[dGVzdDE6dGVzdDE= dGVzdDI6dGVzdDI=] }
78
78
  client = BridgeAPI::Client.new(config)
79
79
  new_config = client.initialize_from_token_pool(config)
80
- expect(new_config[:token]).to eq('token1')
80
+ expect(new_config[:api_key]).to eq('test1')
81
81
  end
82
82
  end
83
83
 
@@ -111,15 +111,16 @@ describe BridgeAPI::Client do
111
111
 
112
112
  context 'with token pool' do
113
113
  it 'should rotate to the next key' do
114
- keys = %w[token1 token2]
114
+ keys = %w[dGVzdDE6dGVzdDE= dGVzdDI6dGVzdDI=]
115
115
  config = { prefix: 'https://www.fake.com', api_tokens: keys }
116
116
  client = BridgeAPI::Client.new(config)
117
- expect(client.config[:token]).to eq('token1')
117
+ expect(client.config[:api_key]).to eq('test1')
118
118
  client.rotate_token!
119
- expect(client.config[:token]).to eq('token2')
119
+ expect(client.config[:api_key]).to eq('test2')
120
120
  client.rotate_token!
121
- expect(client.config[:token]).to eq('token1')
121
+ expect(client.config[:api_key]).to eq('test1')
122
122
  end
123
123
  end
124
124
  end
125
125
  end
126
+
@@ -60,6 +60,10 @@ class FakeBridge < Sinatra::Base
60
60
  get_json_data 204, nil
61
61
  end
62
62
 
63
+ post %r{/api/author/programs/\d+/completions} do
64
+ get_json_data 204, nil
65
+ end
66
+
63
67
  delete %r{/api/author/programs/\d+/learners} do
64
68
  get_json_data 204, nil
65
69
  end
metadata CHANGED
@@ -1,35 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bridge_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.63
4
+ version: 0.1.68
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jay Shaffer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-19 00:00:00.000000000 Z
11
+ date: 2022-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 1.0.0
20
17
  - - "~>"
21
18
  - !ruby/object:Gem::Version
22
19
  version: '1.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.0
23
23
  type: :development
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 1.0.0
30
27
  - - "~>"
31
28
  - !ruby/object:Gem::Version
32
29
  version: '1.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: byebug
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -104,22 +104,22 @@ dependencies:
104
104
  name: tilt
105
105
  requirement: !ruby/object:Gem::Requirement
106
106
  requirements:
107
- - - "~>"
108
- - !ruby/object:Gem::Version
109
- version: '1.3'
110
107
  - - ">="
111
108
  - !ruby/object:Gem::Version
112
109
  version: 1.3.4
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: '1.3'
113
113
  type: :development
114
114
  prerelease: false
115
115
  version_requirements: !ruby/object:Gem::Requirement
116
116
  requirements:
117
- - - "~>"
118
- - !ruby/object:Gem::Version
119
- version: '1.3'
120
117
  - - ">="
121
118
  - !ruby/object:Gem::Version
122
119
  version: 1.3.4
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '1.3'
123
123
  - !ruby/object:Gem::Dependency
124
124
  name: webmock
125
125
  requirement: !ruby/object:Gem::Requirement
@@ -197,6 +197,7 @@ executables: []
197
197
  extensions: []
198
198
  extra_rdoc_files: []
199
199
  files:
200
+ - ".github/workflows/ci.yml"
200
201
  - ".gitignore"
201
202
  - Dockerfile
202
203
  - Gemfile
@@ -297,7 +298,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
298
  - !ruby/object:Gem::Version
298
299
  version: '0'
299
300
  requirements: []
300
- rubygems_version: 3.0.3
301
+ rubygems_version: 3.1.6
301
302
  signing_key:
302
303
  specification_version: 4
303
304
  summary: Bridge API