bridge_api 0.1.62 → 0.1.67

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 17bea850599cfc023bac82b3c3b03e2794526ec5
4
- data.tar.gz: f2331ab7a08c8c827a83698cb07390a1845671a4
2
+ SHA256:
3
+ metadata.gz: 29e3ffa7d065173483c89fd670fffd88f8b097a9aa90738bbcb89dee537f9b26
4
+ data.tar.gz: ba246bb521802607f2c7b54796fe3fe37b4e0b54f11f8d034c020d58a7b13604
5
5
  SHA512:
6
- metadata.gz: cd3c6fe25490edf7a4b04465c75b73f0cfd5d649a9fbe7c023a1871bc215b1f739f197325594e64b03d9bfcc38cd165229bb74ce91ac16c405cbd01e4efb9908
7
- data.tar.gz: 62f3327ff1187961ad806ba3bc2eef0ecc41f337b3270219127667d91b1c57d5f80d3eb914717a9f93db0e1e0765342124c8454514ebea3372f5fcfcb7dad1a0
6
+ metadata.gz: 0b7cd2e583404e406976e7a6fa264900125772525ab11caa3397d47466c052e825b5f91dc119edcc7fd98990a2b2603afa17c5f083ea57f0e9928855be32ca65
7
+ data.tar.gz: e63612d06e0049d375ffbcb63e7b912a1baea391565680b5bbcbb6e7aa2fbe43b5cdfe37080d545cb4e7a5ea62feea7b86a8f50d73306504b3de1ad6457dc88b
@@ -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
@@ -11,6 +11,10 @@ module BridgeAPI
11
11
  post("#{API_PATH}#{AUTHOR_PATH}#{PROGRAM_PATH}/#{program_id}#{PROGRAM_ENROLLMENT_PATH}", params)
12
12
  end
13
13
 
14
+ def update_program_enrollment(program_id, program_enrollment_id, params = {})
15
+ put("#{API_PATH}#{AUTHOR_PATH}#{PROGRAM_PATH}/#{program_id}#{PROGRAM_ENROLLMENT_PATH}/#{program_enrollment_id}", params)
16
+ end
17
+
14
18
  def delete_program_enrollment(program_id, enrollment_id, params = {})
15
19
  delete("#{API_PATH}#{AUTHOR_PATH}#{PROGRAM_PATH}/#{program_id}#{PROGRAM_ENROLLMENT_PATH}/#{enrollment_id}", params)
16
20
  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,46 @@ 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
+ old_api_key = config[:api_key]
99
+ keys = config[:api_keys].keys
100
+ return if keys.count <= 1
101
+ key = get_next_key(keys, config[:api_key])
102
+ config[:api_key] = key
103
+ config[:api_secret] = config[:api_keys][key]
104
+ set_connection(config)
105
+ BridgeAPI.logger.debug("ROTATED TO KEY: #{config[:api_key]}")
116
106
  end
117
107
 
118
108
  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]
109
+ keys.delete(current_key)
110
+ usable_key = keys.find do |key|
111
+ limit = rate_limit(key)
112
+ current_key_limit = limit.present? ? limit.fetch('current') : 0
113
+ BridgeAPI.beginning_rate_limit - current_key_limit > BridgeAPI.rate_limit_threshold
114
+ end
115
+ usable_key || keys[rand(keys.length)]
122
116
  end
123
117
 
124
118
  def rate_limit_reached?
@@ -130,14 +124,13 @@ module BridgeAPI
130
124
  def enforce_rate_limits
131
125
  return unless rate_limit_reached?
132
126
 
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)
127
+ rotate_token!
128
+ if rate_limit_reached?
129
+ sleep_time = rand(BridgeAPI.max_sleep_seconds)
130
+ sleep_time = BridgeAPI.min_sleep_seconds if sleep_time < BridgeAPI.min_sleep_seconds
131
+ BridgeAPI.logger.debug("Rate limit reached sleeping for #{sleep_time}")
132
+ sleep(sleep_time)
133
+ end
141
134
  end
142
135
 
143
136
  def using_master_rate_limit?
@@ -148,20 +141,18 @@ module BridgeAPI
148
141
  limit = response.headers['x-rate-limit-remaining']
149
142
  return if limit.nil?
150
143
 
151
- BridgeAPI.logger.debug("BRIDGE RATE LIMIT REMAINING: #{limit}")
144
+ BridgeAPI.logger.debug("BRIDGE RATE LIMIT REMAINING: #{limit} for key #{config[:api_key]}")
152
145
  self.limit_remaining = limit.to_i
153
146
  end
154
147
 
155
148
  def limit_remaining
156
149
  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']
150
+ limit = rate_limit(config[:api_key])
151
+ if limit.nil?
152
+ set_rate_limit(config[:api_key], 0)
153
+ limit = { current: 0 }.with_indifferent_access
164
154
  end
155
+ limit['current']
165
156
  else
166
157
  BridgeAPI.rate_limits[config[:api_key]]
167
158
  end
@@ -169,12 +160,36 @@ module BridgeAPI
169
160
 
170
161
  def limit_remaining=(value)
171
162
  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
163
+ set_rate_limit(config[:api_key], value)
175
164
  else
176
165
  BridgeAPI.rate_limits[config[:api_key]] = value
177
166
  end
167
+ refresh_stale_tokens
168
+ end
169
+
170
+ def set_rate_limit(key, limit)
171
+ BridgeAPI.master_mutex.synchronize do
172
+ PaulWalker::RateLimit.add(key, key, limit, BridgeAPI.beginning_rate_limit)
173
+ end
174
+ end
175
+
176
+ def rate_limit(key)
177
+ BridgeAPI.master_mutex.synchronize do
178
+ PaulWalker::RateLimit.get(key, key)
179
+ end
180
+ end
181
+
182
+ def refresh_stale_tokens
183
+ return unless using_master_rate_limit?
184
+ api_keys = config[:api_keys].keys
185
+ api_keys.delete(config[:api_key])
186
+ api_keys.each do |key|
187
+ limit = rate_limit(key)
188
+ if limit['timestamp'].present? && DateTime.parse(limit['timestamp']) < 1.minute.ago
189
+ BridgeAPI.logger.debug("Refreshing: #{key}")
190
+ set_rate_limit(key, 0)
191
+ end
192
+ end
178
193
  end
179
194
 
180
195
  def set_connection(config)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BridgeAPI
4
- VERSION = '0.1.62' unless defined?(BridgeAPI::VERSION)
4
+ VERSION = '0.1.67' 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))
@@ -12,6 +12,11 @@ describe BridgeAPI::Client::ProgramEnrollment do
12
12
  expect(response.status).to(eq(204))
13
13
  end
14
14
 
15
+ it 'should udpate a program enrollment' do
16
+ response = @client.update_program_enrollment(1, 1)
17
+ expect(response.status).to(eq(200))
18
+ end
19
+
15
20
  it 'should delete a program enrollment' do
16
21
  response = @client.delete_program_enrollment(1, 2)
17
22
  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
+
@@ -52,10 +52,18 @@ class FakeBridge < Sinatra::Base
52
52
  get_json_data 200, 'program_enrollments.json'
53
53
  end
54
54
 
55
+ put %r{/api/author/programs/\d+/learners/\d+} do
56
+ get_json_data 200, 'program_enrollments.json'
57
+ end
58
+
55
59
  post %r{/api/author/programs/\d+/learners} do
56
60
  get_json_data 204, nil
57
61
  end
58
62
 
63
+ post %r{/api/author/programs/\d+/completions} do
64
+ get_json_data 204, nil
65
+ end
66
+
59
67
  delete %r{/api/author/programs/\d+/learners} do
60
68
  get_json_data 204, nil
61
69
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bridge_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.62
4
+ version: 0.1.67
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jay Shaffer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-01 00:00:00.000000000 Z
11
+ date: 2022-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -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
@@ -282,7 +283,7 @@ homepage: https://getbridge.com
282
283
  licenses:
283
284
  - MIT
284
285
  metadata: {}
285
- post_install_message:
286
+ post_install_message:
286
287
  rdoc_options: []
287
288
  require_paths:
288
289
  - lib
@@ -297,9 +298,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
298
  - !ruby/object:Gem::Version
298
299
  version: '0'
299
300
  requirements: []
300
- rubyforge_project:
301
- rubygems_version: 2.5.2.1
302
- signing_key:
301
+ rubygems_version: 3.1.6
302
+ signing_key:
303
303
  specification_version: 4
304
304
  summary: Bridge API
305
305
  test_files: