conversant 1.0.19 → 1.0.20

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -1
  3. data/.gitlab-ci.yml +12 -1
  4. data/.rubocop.yml +43 -6
  5. data/CHANGELOG.md +11 -0
  6. data/Gemfile +1 -1
  7. data/Rakefile +1 -1
  8. data/conversant.gemspec +7 -2
  9. data/examples/inheritance_integration.rb +1 -4
  10. data/lib/conversant/configuration.rb +17 -19
  11. data/lib/conversant/v3/base.rb +1 -1
  12. data/lib/conversant/v3/http_client.rb +29 -31
  13. data/lib/conversant/v3/mixins/authentication.rb +1 -1
  14. data/lib/conversant/v3/services/cdn/analytics.rb +11 -19
  15. data/lib/conversant/v3/services/cdn/audit.rb +1 -1
  16. data/lib/conversant/v3/services/cdn/certificate.rb +1 -1
  17. data/lib/conversant/v3/services/cdn/dashboard.rb +1 -1
  18. data/lib/conversant/v3/services/cdn/domain.rb +6 -7
  19. data/lib/conversant/v3/services/cdn/monitoring.rb +1 -1
  20. data/lib/conversant/v3/services/cdn/partner/analytics.rb +1 -1
  21. data/lib/conversant/v3/services/cdn/partner.rb +1 -1
  22. data/lib/conversant/v3/services/lms/dashboard.rb +3 -5
  23. data/lib/conversant/v3/services/lms/domain.rb +1 -1
  24. data/lib/conversant/v3/services/lms/job.rb +2 -0
  25. data/lib/conversant/v3/services/lms/partner/analytics.rb +1 -1
  26. data/lib/conversant/v3/services/lms/partner.rb +1 -1
  27. data/lib/conversant/v3/services/lms/preset.rb +1 -1
  28. data/lib/conversant/v3/services/lms.rb +0 -2
  29. data/lib/conversant/v3/services/oss/partner/analytics.rb +1 -1
  30. data/lib/conversant/v3/services/oss/partner.rb +1 -1
  31. data/lib/conversant/v3/services/oss.rb +1 -1
  32. data/lib/conversant/v3/services/portal/dashboard.rb +1 -1
  33. data/lib/conversant/v3/services/portal.rb +1 -1
  34. data/lib/conversant/v3/services/vms/analytics.rb +1 -1
  35. data/lib/conversant/v3/services/vms/business.rb +1 -1
  36. data/lib/conversant/v3/services/vms/partner/analytics.rb +1 -1
  37. data/lib/conversant/v3/services/vms/transcoding.rb +3 -3
  38. data/lib/conversant/v3.rb +1 -1
  39. data/lib/conversant/version.rb +1 -1
  40. data/plans/templates/bug-fix-template.md +69 -0
  41. data/plans/templates/feature-implementation-template.md +84 -0
  42. data/plans/templates/refactor-template.md +82 -0
  43. data/plans/templates/template-usage-guide.md +58 -0
  44. data/sig/conversant/v3.rbs +1 -1
  45. metadata +11 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0bd892aa072333932ee783013051c4a32fc77875c71b128a080e717bdfca10a
4
- data.tar.gz: b79962fb9f3d80b38b966f9eb14e9ae9192e2e97daae9f0ba998e7cfb5a422c0
3
+ metadata.gz: 428022316d577c54962bff0866371094de01192f3ffd3bdb4bf163dabd7eb55f
4
+ data.tar.gz: 146d4c61baedbc413f96bbd86262ec893263526956ea659d2d2489cb0e44e8b8
5
5
  SHA512:
6
- metadata.gz: 5f4ad35b17ed3670a73f71c0b67dc9f20ed09384e15c0c42895ff611f4817a3a1f2cbb2e4af4b534bf953a2e83e3d7a6b067afa937c5e33ec00c8f5709b1b71a
7
- data.tar.gz: f47decce579d7da1b863fd8b580b5972970fd266fe623a204ef8c38f96974f6ba87928cfe7d72a7ea5fbcac671d9b72e1a838bf6707b59006c425109aebcab15
6
+ metadata.gz: 8cec1bf543d749082ce25aaf7a36b9c58083168d4e360b26d559571f85a7110d2572fe261d6743bd505ae2ba90facfd0a1e629e7829a0a7cb78b62ed7f47f8ec
7
+ data.tar.gz: 246e1ef85cb4705d8dd61e64ae125b49cbb1caf2f93aeffe9d5d0b41c5bc1ae77bfaee393ce9a8bbe46653c29494719d657534447d274ab06104a9c9f460a4ac
data/.gitignore CHANGED
@@ -49,4 +49,8 @@ Gemfile.lock
49
49
 
50
50
  # Test artifacts
51
51
  /spec/cassettes/
52
- /spec/fixtures/vcr_cassettes/
52
+ /spec/fixtures/vcr_cassettes/
53
+ /.claude/
54
+ /.repomixignore
55
+ /CLAUDE.md
56
+ /release-manifest.json
data/.gitlab-ci.yml CHANGED
@@ -7,6 +7,16 @@ stages:
7
7
 
8
8
  variables:
9
9
  BUNDLE_PATH: vendor/bundle
10
+ GIT_STRATEGY: clone
11
+ GIT_DEPTH: "20"
12
+
13
+ # Wipe runner build dir before clone — prevents stale ref conflicts
14
+ # (e.g. "couldn't set 'refs/remotes/origin/rebases/ci'" when
15
+ # a sibling ref like 'rebases' exists from earlier jobs).
16
+ default:
17
+ hooks:
18
+ pre_get_sources_script:
19
+ - rm -rf "$CI_PROJECT_DIR/.git" || true
10
20
 
11
21
  # Cache dependencies
12
22
  cache:
@@ -19,8 +29,9 @@ cache:
19
29
  .ruby_template: &ruby_template
20
30
  image: ruby:3.0
21
31
  before_script:
22
- - gem install bundler -v 2.4.0
32
+ - gem install bundler -v 2.4.22
23
33
  - bundle config set --local path 'vendor/bundle'
34
+ - mkdir -p vendor/bundle
24
35
  - bundle install --jobs $(nproc)
25
36
 
26
37
  # Run tests on multiple Ruby versions
data/.rubocop.yml CHANGED
@@ -1,16 +1,53 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7
3
+ NewCops: disable
4
+ SuggestExtensions: false
5
+ Exclude:
6
+ - 'vendor/**/*'
7
+ - 'tmp/**/*'
8
+ - 'examples/**/*' # demo scripts, not production
9
+ - 'spec/**/*'
10
+ - 'bin/**/*'
11
+ - 'exe/**/*'
12
+
13
+ # --- Layout ---
1
14
  Layout/EndOfLine:
2
- Enabled: False
15
+ Enabled: false
16
+ Layout/LineLength:
17
+ Max: 150 # vendor API URLs / long YARD comments
18
+
19
+ # --- Metrics — service classes wrap vendor APIs, tend to be long ---
3
20
  Metrics/MethodLength:
4
- Max: 50
21
+ Max: 60
5
22
  Metrics/AbcSize:
6
- Max: 50
23
+ Max: 70
7
24
  CountRepeatedAttributes: false
8
25
  Metrics/CyclomaticComplexity:
9
- Max: 10
26
+ Max: 25
27
+ Metrics/PerceivedComplexity:
28
+ Max: 25
10
29
  Metrics/ClassLength:
11
30
  Max: 500
31
+ Metrics/ModuleLength:
32
+ Max: 300
33
+ Metrics/BlockLength:
34
+ Max: 50
12
35
  Metrics/ParameterLists:
13
36
  Max: 5
14
37
  MaxOptionalParameters: 5
15
- Metrics/PerceivedComplexity:
16
- Max: 10
38
+
39
+ # --- Style — only disable cops that conflict with codebase reality ---
40
+ Style/Documentation:
41
+ Enabled: false # too many classes, low ROI
42
+ Style/GlobalVars:
43
+ Enabled: false # $redis is Rails convention
44
+ Style/StringLiterals:
45
+ EnforcedStyle: single_quotes
46
+ ConsistentQuotesInMultiline: false
47
+
48
+ # --- Naming — vendor API forces non-conformant names ---
49
+ Naming/VariableNumber:
50
+ Enabled: false # mDataProp_0, bSortable_1 are vendor API params
51
+ Naming/AccessorMethodName:
52
+ Exclude:
53
+ - '**/lms/dashboard.rb' # parses HTML tables, get_X reads better
data/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.20] - 2026-07-04
9
+
10
+ ### Fixed
11
+ - **SSO Login URL hardcoded to dead domain**: `V3::HttpClient` used a hardcoded `LOGIN_URL = 'https://console.swiftfederation.com/'` constant, ignoring configuration. After the domain migration to `flashcone.com`, SSO login failed with `Connection refused`, producing `Missing SESSION for CDN` and empty usages responses.
12
+ - Removed the `LOGIN_URL` constant; added private `#login_url` resolving from `configuration.portal_endpoint` (ENV-driven: `PRIVATE_PORTAL_ENDPOINT` / `PORTAL_ROOT_HOSTNAME`), normalized with a single trailing slash
13
+ - Login page URL is now fully config/ENV-driven — no base domain hardcoded
14
+
15
+ ### Changed
16
+ - **Default portal/SSO hostnames** in `Configuration` updated from the retired `*.swiftfederation.com` to `console.flashcone.com` / `sso2.flashcone.com` (only used when ENV is unset)
17
+ - Updated RBS in `sig/conversant/v3.rbs`: removed `LOGIN_URL` constant, added `login_url` method signature
18
+
8
19
  ## [1.0.19] - 2026-02-06
9
20
 
10
21
  ### Fixed
data/Gemfile CHANGED
@@ -9,4 +9,4 @@ gemspec
9
9
  group :development, :test do
10
10
  gem 'pry-byebug'
11
11
  gem 'simplecov', require: false
12
- end
12
+ end
data/Rakefile CHANGED
@@ -18,4 +18,4 @@ task :console do
18
18
  require 'conversant'
19
19
  require 'pry'
20
20
  Pry.start
21
- end
21
+ end
data/conversant.gemspec CHANGED
@@ -9,7 +9,12 @@ Gem::Specification.new do |spec|
9
9
  spec.email = ['tho.nguyen@vnetwork.vn']
10
10
 
11
11
  spec.summary = 'Ruby client for Conversant CDN API (V3)'
12
- spec.description = 'A Ruby gem providing a clean interface to interact with Conversant/SwiftFederation CDN services including Portal, CDN, and LMS APIs with V3 authentication support. Features zero-configuration setup, automatic ENV detection, and seamless integration with existing Conversant implementations.'
12
+ spec.description = <<~DESC
13
+ A Ruby gem providing a clean interface to interact with Conversant/SwiftFederation
14
+ CDN services including Portal, CDN, and LMS APIs with V3 authentication support.
15
+ Features zero-configuration setup, automatic ENV detection, and seamless integration
16
+ with existing Conversant implementations.
17
+ DESC
13
18
  spec.homepage = 'https://gitlab.vnetwork.dev/gems/conversant'
14
19
  spec.license = 'MIT'
15
20
  spec.required_ruby_version = '>= 2.7.0'
@@ -39,7 +44,7 @@ Gem::Specification.new do |spec|
39
44
 
40
45
  # Development dependencies
41
46
  spec.add_development_dependency 'bundler', '~> 2.0'
42
- spec.add_development_dependency 'pry', '~> 0.14'
47
+ spec.add_development_dependency 'pry', '~> 0.14.0'
43
48
  spec.add_development_dependency 'rake', '~> 13.0'
44
49
  spec.add_development_dependency 'rspec', '~> 3.12'
45
50
  spec.add_development_dependency 'rubocop', '~> 1.50'
@@ -193,10 +193,7 @@ module Utils
193
193
 
194
194
  private
195
195
 
196
- def configuration
197
- ::Conversant.configuration
198
- end
199
-
196
+ # `configuration` is already provided by the Authentication mixin (private).
200
197
  def logger
201
198
  configuration.logger
202
199
  end
@@ -26,8 +26,8 @@ module Conversant
26
26
  # Users only need to override if they want different values
27
27
 
28
28
  # Portal configuration - defaults from ENV
29
- @portal_root_hostname = ENV['PORTAL_ROOT_HOSTNAME'] || 'console.swiftfederation.com'
30
- @portal_sso_hostname = ENV['PORTAL_SSO_HOSTNAME'] || 'sso.swiftfederation.com'
29
+ @portal_root_hostname = ENV['PORTAL_ROOT_HOSTNAME'] || 'console.flashcone.com'
30
+ @portal_sso_hostname = ENV['PORTAL_SSO_HOSTNAME'] || 'sso2.flashcone.com'
31
31
 
32
32
  # API Endpoints - defaults from ENV (matching existing CONVERSANT setup)
33
33
  @private_cdn_endpoint = ENV['PRIVATE_CDN_ENDPOINT']
@@ -56,14 +56,12 @@ module Conversant
56
56
 
57
57
  # Redis - auto-detect from global $redis or ENV
58
58
  @redis = if defined?($redis) && $redis
59
- $redis
60
- elsif defined?(::Redis) && ENV['REDIS_URL']
61
- ::Redis.new(url: ENV['REDIS_URL'])
62
- elsif defined?(::Redis)
63
- ::Redis.new
64
- else
65
- nil
66
- end
59
+ $redis
60
+ elsif defined?(::Redis) && ENV['REDIS_URL']
61
+ ::Redis.new(url: ENV['REDIS_URL'])
62
+ elsif defined?(::Redis)
63
+ ::Redis.new
64
+ end
67
65
  end
68
66
 
69
67
  def portal_endpoint
@@ -79,18 +77,18 @@ module Conversant
79
77
 
80
78
  # Only require credentials if not auto-configured
81
79
  unless auto_configured?
82
- required_fields += [
83
- :swiftserve_identifier_id,
84
- :swiftserve_identifier_hash
80
+ required_fields += %i[
81
+ swiftserve_identifier_id
82
+ swiftserve_identifier_hash
85
83
  ]
86
84
  end
87
85
 
88
86
  # Always require endpoints
89
- required_fields += [
90
- :private_cdn_endpoint,
91
- :private_lms_endpoint,
92
- :private_vms_endpoint,
93
- :redis
87
+ required_fields += %i[
88
+ private_cdn_endpoint
89
+ private_lms_endpoint
90
+ private_vms_endpoint
91
+ redis
94
92
  ]
95
93
 
96
94
  missing_fields = required_fields.select { |field| send(field).nil? || send(field).to_s.empty? }
@@ -129,4 +127,4 @@ module Conversant
129
127
  config
130
128
  end
131
129
  end
132
- end
130
+ end
@@ -44,4 +44,4 @@ module Conversant
44
44
  end
45
45
  end
46
46
  end
47
- end
47
+ end
@@ -20,9 +20,6 @@ module Conversant
20
20
  #
21
21
  # @since 1.0.0
22
22
  module HttpClient
23
- # Default login URL for SSO authentication
24
- LOGIN_URL = 'https://console.swiftfederation.com/'
25
-
26
23
  # Redis key for storing portal SESSION cookie
27
24
  PORTAL_SESSION_REDIS_KEY = 'CONVERSANT.V3.PORTAL.SESSION'
28
25
 
@@ -115,8 +112,8 @@ module Conversant
115
112
  self.cookie_jar = {}
116
113
 
117
114
  # Step 1: Get login page and extract form action
118
- debug_log "[SSO Login - Step 1] Fetching login page: #{LOGIN_URL}"
119
- login_page_response = http_get(LOGIN_URL)
115
+ debug_log "[SSO Login - Step 1] Fetching login page: #{login_url}"
116
+ login_page_response = http_get(login_url)
120
117
  return nil unless login_page_response
121
118
 
122
119
  debug_log "Login page loaded, status: #{login_page_response[:status]}"
@@ -139,8 +136,8 @@ module Conversant
139
136
 
140
137
  debug_log '[SSO Login - Step 3] Submitting login form...'
141
138
  login_response = http_post(form_action, form_data, {
142
- 'Content-Type' => 'application/x-www-form-urlencoded'
143
- })
139
+ 'Content-Type' => 'application/x-www-form-urlencoded'
140
+ })
144
141
 
145
142
  return nil unless login_response
146
143
 
@@ -165,7 +162,6 @@ module Conversant
165
162
 
166
163
  debug_log 'New SSO login failed - no valid sessions obtained'
167
164
  nil
168
-
169
165
  rescue StandardError => e
170
166
  configuration.logger.error "Conversant::V3 - SSO login error: #{e.message}"
171
167
  nil
@@ -191,13 +187,11 @@ module Conversant
191
187
  def authenticate
192
188
  result = sso_login
193
189
 
194
- if result && result[:session]
195
- redis.set(PORTAL_SESSION_REDIS_KEY, result[:session], ex: configuration.cache_ttl)
196
- redis.set(SSO_GW_SESSION2_REDIS_KEY, result[:sso_gw_session2], ex: configuration.cache_ttl) if result[:sso_gw_session2]
197
- result
198
- else
199
- nil
200
- end
190
+ return unless result && result[:session]
191
+
192
+ redis.set(PORTAL_SESSION_REDIS_KEY, result[:session], ex: configuration.cache_ttl)
193
+ redis.set(SSO_GW_SESSION2_REDIS_KEY, result[:sso_gw_session2], ex: configuration.cache_ttl) if result[:sso_gw_session2]
194
+ result
201
195
  end
202
196
 
203
197
  # Makes an authenticated HTTP request
@@ -224,9 +218,7 @@ module Conversant
224
218
  debug_log "[Request] Headers: #{headers.inspect}" if configuration.debug_mode
225
219
 
226
220
  # Check Content-Type using string key (headers are now plain hash with string keys)
227
- if headers&.[]('Content-Type') == configuration.default_content_type
228
- payload = payload&.to_json
229
- end
221
+ payload = payload&.to_json if headers&.[]('Content-Type') == configuration.default_content_type
230
222
 
231
223
  request = RestClient::Request.new(
232
224
  method: method,
@@ -270,7 +262,7 @@ module Conversant
270
262
  headers: headers,
271
263
  verify_ssl: configuration.verify_ssl,
272
264
  max_redirects: 10
273
- ) do |resp, request, result|
265
+ ) do |resp, _request, _result|
274
266
  update_cookie_jar(resp)
275
267
 
276
268
  case resp.code
@@ -319,7 +311,7 @@ module Conversant
319
311
  headers: headers,
320
312
  verify_ssl: configuration.verify_ssl,
321
313
  max_redirects: 10
322
- ) do |resp, request, result|
314
+ ) do |resp, _request, _result|
323
315
  update_cookie_jar(resp)
324
316
 
325
317
  case resp.code
@@ -387,10 +379,10 @@ module Conversant
387
379
  end
388
380
  end
389
381
 
390
- if response.respond_to?(:cookies) && response.cookies.is_a?(Hash)
391
- response.cookies.each do |name, value|
392
- cookie_jar[name.to_s] = value.to_s
393
- end
382
+ return unless response.respond_to?(:cookies) && response.cookies.is_a?(Hash)
383
+
384
+ response.cookies.each do |name, value|
385
+ cookie_jar[name.to_s] = value.to_s
394
386
  end
395
387
  end
396
388
 
@@ -425,19 +417,25 @@ module Conversant
425
417
 
426
418
  # Parse HTML to find form action URL
427
419
  match = html_body.match(/form[^>]*id=["']kc-form-login["'][^>]*action=["']([^"']*)["']/)
428
- if match
429
- return CGI.unescapeHTML(match[1])
430
- end
420
+ return CGI.unescapeHTML(match[1]) if match
431
421
 
432
422
  # Fallback: look for any form with action
433
423
  match = html_body.match(/form[^>]*action=["']([^"']*)["']/)
434
- if match
435
- return CGI.unescapeHTML(match[1])
436
- end
424
+ return CGI.unescapeHTML(match[1]) if match
437
425
 
438
426
  nil
439
427
  end
440
428
 
429
+ # Returns the SSO login page URL (portal console root)
430
+ #
431
+ # Resolved from configuration (ENV-driven) so the base domain is never
432
+ # hardcoded. Always normalized with a single trailing slash.
433
+ #
434
+ # @return [String] Login page URL, e.g. "https://console.flashcone.com/"
435
+ def login_url
436
+ "#{configuration.portal_endpoint.chomp('/')}/"
437
+ end
438
+
441
439
  # Returns the global Conversant configuration
442
440
  #
443
441
  # @return [Conversant::Configuration] Configuration instance
@@ -453,4 +451,4 @@ module Conversant
453
451
  end
454
452
  end
455
453
  end
456
- end
454
+ end
@@ -218,4 +218,4 @@ module Conversant
218
218
  end
219
219
  end
220
220
  end
221
- end
221
+ end
@@ -75,7 +75,7 @@ module Conversant
75
75
  if response&.[]('bandwidth')
76
76
  {
77
77
  averageThroughput: response&.[]('averageThroughput'),
78
- data: response&.[]('bandwidth'),
78
+ data: response&.[]('bandwidth')
79
79
  }
80
80
  end
81
81
  rescue StandardError => e
@@ -170,7 +170,7 @@ module Conversant
170
170
  avgQuantity: response['avgQuantity'],
171
171
  httpTotalQuantity: response['httpTotalQuantity'],
172
172
  httpsTotalQuantity: response['httpsTotalQuantity'],
173
- data: response['datas'],
173
+ data: response['datas']
174
174
  }
175
175
  end
176
176
  rescue StandardError => e
@@ -312,8 +312,7 @@ module Conversant
312
312
  # @since 1.0.8
313
313
  def request_hit_rate(params)
314
314
  query_string = URI.encode_www_form(params)
315
- response = JSON.parse(@parent.send(:call, 'GET', "/api/hit_rate_chart_data?#{query_string}", nil))
316
- response
315
+ JSON.parse(@parent.send(:call, 'GET', "/api/hit_rate_chart_data?#{query_string}", nil))
317
316
  rescue StandardError => e
318
317
  @parent.send(:logger).error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
319
318
  nil
@@ -326,8 +325,7 @@ module Conversant
326
325
  # @since 1.0.8
327
326
  def byte_hit_rate(params)
328
327
  query_string = URI.encode_www_form(params)
329
- response = JSON.parse(@parent.send(:call, 'GET', "/api/byte_rate_chart_data?#{query_string}", nil))
330
- response
328
+ JSON.parse(@parent.send(:call, 'GET', "/api/byte_rate_chart_data?#{query_string}", nil))
331
329
  rescue StandardError => e
332
330
  @parent.send(:logger).error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
333
331
  nil
@@ -379,8 +377,7 @@ module Conversant
379
377
  # @return [Hash, nil] total referrer data, or nil on error
380
378
  # @since 1.0.8
381
379
  def referrer_total_by_domain(payload)
382
- response = JSON.parse(@parent.send(:call, 'POST', '/api/report/getReferrerTotalByDomain', payload))
383
- response
380
+ JSON.parse(@parent.send(:call, 'POST', '/api/report/getReferrerTotalByDomain', payload))
384
381
  rescue StandardError => e
385
382
  @parent.send(:logger).error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
386
383
  nil
@@ -392,8 +389,7 @@ module Conversant
392
389
  # @return [String, nil] CSV data, or nil on error
393
390
  # @since 1.0.8
394
391
  def referrer_csv_by_domain(payload)
395
- response = @parent.send(:call, 'POST', '/api/report/getReferrerCsvByDomain', payload)
396
- response
392
+ @parent.send(:call, 'POST', '/api/report/getReferrerCsvByDomain', payload)
397
393
  rescue StandardError => e
398
394
  @parent.send(:logger).error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
399
395
  nil
@@ -405,8 +401,7 @@ module Conversant
405
401
  # @return [Hash, nil] total URL data, or nil on error
406
402
  # @since 1.0.8
407
403
  def popular_content_url_total_by_domain(payload)
408
- response = JSON.parse(@parent.send(:call, 'POST', '/api/report/getUrlTotalByDomain', payload))
409
- response
404
+ JSON.parse(@parent.send(:call, 'POST', '/api/report/getUrlTotalByDomain', payload))
410
405
  rescue StandardError => e
411
406
  @parent.send(:logger).error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
412
407
  nil
@@ -418,8 +413,7 @@ module Conversant
418
413
  # @return [String, nil] CSV data, or nil on error
419
414
  # @since 1.0.8
420
415
  def popular_content_url_csv_by_domain(payload)
421
- response = @parent.send(:call, 'POST', '/api/report/getUrlCsvByDomain', payload)
422
- response
416
+ @parent.send(:call, 'POST', '/api/report/getUrlCsvByDomain', payload)
423
417
  rescue StandardError => e
424
418
  @parent.send(:logger).error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
425
419
  nil
@@ -431,8 +425,7 @@ module Conversant
431
425
  # @return [Hash, nil] request/visit statistics, or nil on error
432
426
  # @since 1.0.8
433
427
  def request_visit_number(payload)
434
- response = JSON.parse(@parent.send(:call, 'POST', '/api/report/request_visit_number', payload))
435
- response
428
+ JSON.parse(@parent.send(:call, 'POST', '/api/report/request_visit_number', payload))
436
429
  rescue StandardError => e
437
430
  @parent.send(:logger).error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
438
431
  nil
@@ -470,8 +463,7 @@ module Conversant
470
463
  # @return [String, nil] CSV data, or nil on error
471
464
  # @since 1.0.8
472
465
  def user_agent_csv(payload)
473
- response = @parent.send(:call, 'POST', '/api/report/domain/userAgentCsv', payload)
474
- response
466
+ @parent.send(:call, 'POST', '/api/report/domain/userAgentCsv', payload)
475
467
  rescue StandardError => e
476
468
  @parent.send(:logger).error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
477
469
  nil
@@ -480,4 +472,4 @@ module Conversant
480
472
  end
481
473
  end
482
474
  end
483
- end
475
+ end
@@ -68,4 +68,4 @@ module Conversant
68
68
  end
69
69
  end
70
70
  end
71
- end
71
+ end
@@ -177,4 +177,4 @@ module Conversant
177
177
  end
178
178
  end
179
179
  end
180
- end
180
+ end
@@ -106,4 +106,4 @@ module Conversant
106
106
  end
107
107
  end
108
108
  end
109
- end
109
+ end
@@ -64,12 +64,11 @@ module Conversant
64
64
  return [] if response.nil?
65
65
 
66
66
  response = JSON.parse(response)
67
- if response&.[]('list')
68
- domains = response['list'].to_json
69
- redis.set(key, domains, ex: 600)
70
- else
71
- return []
72
- end
67
+ return [] unless response&.[]('list')
68
+
69
+ domains = response['list'].to_json
70
+ redis.set(key, domains, ex: 600)
71
+
73
72
  end
74
73
 
75
74
  JSON.parse(domains).map do |item|
@@ -220,4 +219,4 @@ module Conversant
220
219
  end
221
220
  end
222
221
  end
223
- end
222
+ end
@@ -62,4 +62,4 @@ module Conversant
62
62
  end
63
63
  end
64
64
  end
65
- end
65
+ end
@@ -230,4 +230,4 @@ module Conversant
230
230
  end
231
231
  end
232
232
  end
233
- end
233
+ end
@@ -57,4 +57,4 @@ module Conversant
57
57
  end
58
58
 
59
59
  # Load nested analytics class after Partner is defined
60
- require_relative 'partner/analytics'
60
+ require_relative 'partner/analytics'
@@ -56,9 +56,9 @@ module Conversant
56
56
  table = doc.at('table')
57
57
  return [] if table.nil?
58
58
 
59
- jobs = table.search('tr').map do |row|
59
+ table.search('tr').map do |row|
60
60
  cells = row.search('td')
61
- next [] if cells.length.zero?
61
+ next [] if cells.empty?
62
62
 
63
63
  app_name, stream_name, type, created, started, recording, status = cells
64
64
 
@@ -72,8 +72,6 @@ module Conversant
72
72
  status: status&.text&.strip
73
73
  }
74
74
  end.flatten.compact
75
-
76
- jobs
77
75
  rescue StandardError => e
78
76
  logger.error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
79
77
  []
@@ -96,4 +94,4 @@ module Conversant
96
94
  end
97
95
  end
98
96
  end
99
- end
97
+ end
@@ -105,4 +105,4 @@ module Conversant
105
105
  end
106
106
  end
107
107
  end
108
- end
108
+ end
@@ -81,6 +81,7 @@ module Conversant
81
81
  # all_jobs = lms.job.where
82
82
  #
83
83
  # @since 1.0.0
84
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
84
85
  def where(**params)
85
86
  response = JSON.parse(@parent.send(:call, 'GET', "/v4/streams?#{params.to_query}"))
86
87
  return nil unless response
@@ -122,6 +123,7 @@ module Conversant
122
123
  @parent.send(:logger).error "LMS::Job.where error: #{e.message}"
123
124
  nil
124
125
  end
126
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
125
127
 
126
128
  private
127
129
 
@@ -263,4 +263,4 @@ module Conversant
263
263
  end
264
264
  end
265
265
  end
266
- end
266
+ end
@@ -55,4 +55,4 @@ end
55
55
  # Load nested analytics class after Partner is defined
56
56
  require_relative 'partner/analytics'
57
57
  require_relative 'partner/report'
58
- require_relative 'partner/business'
58
+ require_relative 'partner/business'
@@ -54,4 +54,4 @@ module Conversant
54
54
  end
55
55
  end
56
56
  end
57
- end
57
+ end
@@ -104,8 +104,6 @@ module Conversant
104
104
  response.body
105
105
  end
106
106
 
107
- protected
108
-
109
107
  def service_endpoint
110
108
  configuration.private_lms_endpoint
111
109
  end
@@ -102,4 +102,4 @@ module Conversant
102
102
  end
103
103
  end
104
104
  end
105
- end
105
+ end
@@ -45,4 +45,4 @@ module Conversant
45
45
  end
46
46
 
47
47
  # Load nested analytics class after Partner is defined
48
- require_relative 'partner/analytics'
48
+ require_relative 'partner/analytics'
@@ -125,4 +125,4 @@ module Conversant
125
125
  end
126
126
 
127
127
  # Load nested service classes after OSS is defined
128
- require_relative 'oss/partner'
128
+ require_relative 'oss/partner'
@@ -111,4 +111,4 @@ module Conversant
111
111
  end
112
112
  end
113
113
  end
114
- end
114
+ end
@@ -84,7 +84,7 @@ module Conversant
84
84
  deleted: item['deleted'],
85
85
  pop: item['pop'],
86
86
  volume: item['volume'],
87
- federation: item['federationVolume'],
87
+ federation: item['federationVolume']
88
88
  }
89
89
  end.to_json
90
90
 
@@ -111,4 +111,4 @@ module Conversant
111
111
  end
112
112
  end
113
113
  end
114
- end
114
+ end
@@ -187,4 +187,4 @@ module Conversant
187
187
  end
188
188
  end
189
189
  end
190
- end
190
+ end
@@ -130,4 +130,4 @@ module Conversant
130
130
  end
131
131
  end
132
132
  end
133
- end
133
+ end
@@ -108,7 +108,7 @@ module Conversant
108
108
  response = @parent.send(:call, 'GET', "/v2/jobs?#{merged_payload.to_query}")
109
109
  return [] if response.nil?
110
110
 
111
- JSON.parse(response)&.[]("list") || []
111
+ JSON.parse(response)&.[]('list') || []
112
112
  rescue StandardError => e
113
113
  logger.error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
114
114
  []
@@ -163,7 +163,7 @@ module Conversant
163
163
  response = @parent.send(:call, 'GET', "/v2/osp/presets?#{merged_payload.to_query}")
164
164
  return [] if response.nil?
165
165
 
166
- JSON.parse(response)&.[]("list") || []
166
+ JSON.parse(response)&.[]('list') || []
167
167
  rescue StandardError => e
168
168
  logger.error "#{@parent.send(:identifier)}.METHOD:#{__method__}.EXCEPTION:#{e.message}"
169
169
  []
@@ -181,4 +181,4 @@ module Conversant
181
181
  end
182
182
  end
183
183
  end
184
- end
184
+ end
data/lib/conversant/v3.rb CHANGED
@@ -33,4 +33,4 @@ module Conversant
33
33
  end
34
34
  end
35
35
  end
36
- end
36
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Conversant
4
- VERSION = '1.0.19'
4
+ VERSION = '1.0.20'
5
5
  end
@@ -0,0 +1,69 @@
1
+ # [Bug Fix] Implementation Plan
2
+
3
+ **Date**: YYYY-MM-DD
4
+ **Type**: Bug Fix
5
+ **Priority**: [Critical/High/Medium/Low]
6
+ **Context Tokens**: <150 words
7
+
8
+ ## Executive Summary
9
+ Brief description of the bug and its impact.
10
+
11
+ ## Issue Analysis
12
+ ### Symptoms
13
+ - [ ] Symptom 1
14
+ - [ ] Symptom 2
15
+
16
+ ### Root Cause
17
+ Brief explanation of the underlying cause.
18
+
19
+ ### Evidence
20
+ - **Logs**: Reference to log files (don't include full logs)
21
+ - **Error Messages**: Key error patterns
22
+ - **Affected Components**: List of impacted files/modules
23
+
24
+ ## Context Links
25
+ - **Related Issues**: [GitHub issue numbers]
26
+ - **Recent Changes**: [Relevant commits or PRs]
27
+ - **Dependencies**: [Related systems]
28
+
29
+ ## Solution Design
30
+ ### Approach
31
+ High-level fix strategy in 2-3 sentences.
32
+
33
+ ### Changes Required
34
+ 1. **File 1** (`path/to/file.ts`): Brief change description
35
+ 2. **File 2** (`path/to/file.ts`): Brief change description
36
+
37
+ ### Testing Changes
38
+ - [ ] Update existing tests
39
+ - [ ] Add new test cases
40
+ - [ ] Validate fix doesn't break existing functionality
41
+
42
+ ## Implementation Steps
43
+ 1. [ ] Step 1 - file: `path/to/file.ts`
44
+ 2. [ ] Step 2 - file: `path/to/file.ts`
45
+ 3. [ ] Run test suite
46
+ 4. [ ] Validate fix in relevant environments
47
+
48
+ ## Verification Plan
49
+ ### Test Cases
50
+ - [ ] Test case 1: Expected behavior
51
+ - [ ] Test case 2: Edge case handling
52
+ - [ ] Regression test: Ensure no new issues
53
+
54
+ ### Rollback Plan
55
+ If the fix causes issues:
56
+ 1. Revert commit: `git revert <commit-hash>`
57
+ 2. Restore previous behavior in files X, Y, Z
58
+
59
+ ## Risk Assessment
60
+ | Risk | Impact | Mitigation |
61
+ |------|--------|------------|
62
+ | Risk 1 | Medium | Mitigation plan |
63
+
64
+ ## TODO Checklist
65
+ - [ ] Implement fix
66
+ - [ ] Update tests
67
+ - [ ] Run full test suite
68
+ - [ ] Code review
69
+ - [ ] Deploy and verify
@@ -0,0 +1,84 @@
1
+ # [Feature Name] Implementation Plan
2
+
3
+ **Date**: YYYY-MM-DD
4
+ **Type**: Feature Implementation
5
+ **Status**: Planning
6
+ **Context Tokens**: <200 words
7
+
8
+ ## Executive Summary
9
+ Brief 2-3 sentence description of the feature and its business value.
10
+
11
+ ## Context Links
12
+ - **Related Plans**: [List other plan files - no full content]
13
+ - **Dependencies**: [External systems, APIs, existing features]
14
+ - **Reference Docs**: [Link to docs in ./docs directory]
15
+
16
+ ## Requirements
17
+ ### Functional Requirements
18
+ - [ ] Requirement 1
19
+ - [ ] Requirement 2
20
+
21
+ ### Non-Functional Requirements
22
+ - [ ] Performance target
23
+ - [ ] Security requirement
24
+ - [ ] Scalability requirement
25
+
26
+ ## Architecture Overview
27
+ ```mermaid
28
+ [Simple component diagram]
29
+ ```
30
+
31
+ ### Key Components
32
+ - **Component 1**: Brief description
33
+ - **Component 2**: Brief description
34
+
35
+ ### Data Models
36
+ - **Model 1**: Key fields
37
+ - **Model 2**: Key fields
38
+
39
+ ## Implementation Phases
40
+
41
+ ### Phase 1: [Name] (Est: X days)
42
+ **Scope**: Specific boundaries
43
+ **Tasks**:
44
+ 1. [ ] Task 1 - file: `path/to/file.ts`
45
+ 2. [ ] Task 2 - file: `path/to/file.ts`
46
+
47
+ **Acceptance Criteria**:
48
+ - [ ] Criteria 1
49
+ - [ ] Criteria 2
50
+
51
+ ### Phase 2: [Name] (Est: X days)
52
+ [Repeat structure]
53
+
54
+ ## Testing Strategy
55
+ - **Unit Tests**: Specific test coverage targets
56
+ - **Integration Tests**: Key interaction points
57
+ - **E2E Tests**: Critical user flows
58
+
59
+ ## Security Considerations
60
+ - [ ] Security item 1
61
+ - [ ] Security item 2
62
+
63
+ ## Risk Assessment
64
+ | Risk | Impact | Mitigation |
65
+ |------|--------|------------|
66
+ | Risk 1 | High | Mitigation strategy |
67
+
68
+ ## Quick Reference
69
+ ### Key Commands
70
+ ```bash
71
+ npm run command
72
+ ```
73
+
74
+ ### Configuration Files
75
+ - `config/file.ts`: Purpose
76
+ - `.env.example`: Environment variables
77
+
78
+ ## TODO Checklist
79
+ - [ ] Phase 1 Task 1
80
+ - [ ] Phase 1 Task 2
81
+ - [ ] Phase 2 Task 1
82
+ - [ ] Testing complete
83
+ - [ ] Documentation updated
84
+ - [ ] Code review passed
@@ -0,0 +1,82 @@
1
+ # [Component/Module] Refactoring Plan
2
+
3
+ **Date**: YYYY-MM-DD
4
+ **Type**: Refactoring
5
+ **Scope**: [Module/Component/System level]
6
+ **Context Tokens**: <200 words
7
+
8
+ ## Executive Summary
9
+ Brief description of what is being refactored and why.
10
+
11
+ ## Current State Analysis
12
+ ### Issues with Current Implementation
13
+ - [ ] Issue 1: Performance bottleneck
14
+ - [ ] Issue 2: Code maintainability
15
+ - [ ] Issue 3: Technical debt
16
+
17
+ ### Metrics (Before)
18
+ - **Performance**: Current benchmarks
19
+ - **Code Quality**: Complexity metrics
20
+ - **Test Coverage**: Current percentage
21
+
22
+ ## Context Links
23
+ - **Affected Modules**: [List without full content]
24
+ - **Dependencies**: [Other systems impacted]
25
+ - **Related Documentation**: [Links to docs]
26
+
27
+ ## Refactoring Strategy
28
+ ### Approach
29
+ High-level strategy for the refactoring in 2-3 sentences.
30
+
31
+ ### Architecture Changes
32
+ ```mermaid
33
+ [Before/After comparison diagram]
34
+ ```
35
+
36
+ ### Key Improvements
37
+ - **Improvement 1**: Brief description
38
+ - **Improvement 2**: Brief description
39
+
40
+ ## Implementation Plan
41
+
42
+ ### Phase 1: Preparation (Est: X days)
43
+ **Scope**: Setup and preparation work
44
+ 1. [ ] Create comprehensive tests for current functionality
45
+ 2. [ ] Document current behavior
46
+ 3. [ ] Identify all dependencies
47
+
48
+ ### Phase 2: Core Refactoring (Est: X days)
49
+ **Scope**: Main refactoring work
50
+ 1. [ ] Refactor component A - file: `path/to/file.ts`
51
+ 2. [ ] Refactor component B - file: `path/to/file.ts`
52
+ 3. [ ] Update integration points
53
+
54
+ ### Phase 3: Integration & Testing (Est: X days)
55
+ **Scope**: Validation and cleanup
56
+ 1. [ ] Integration testing
57
+ 2. [ ] Performance validation
58
+ 3. [ ] Documentation updates
59
+
60
+ ## Backward Compatibility
61
+ - **Breaking Changes**: [List any breaking changes]
62
+ - **Migration Path**: [Steps for users/systems]
63
+ - **Deprecation Timeline**: [If applicable]
64
+
65
+ ## Success Metrics (After)
66
+ - **Performance**: Target improvements
67
+ - **Code Quality**: Target metrics
68
+ - **Test Coverage**: Target percentage
69
+
70
+ ## Risk Assessment
71
+ | Risk | Impact | Mitigation |
72
+ |------|--------|------------|
73
+ | Breaking changes | High | Comprehensive testing |
74
+ | Performance regression | Medium | Benchmarking |
75
+
76
+ ## TODO Checklist
77
+ - [ ] Phase 1: Preparation complete
78
+ - [ ] Phase 2: Core refactoring complete
79
+ - [ ] Phase 3: Integration complete
80
+ - [ ] Performance benchmarks validated
81
+ - [ ] Documentation updated
82
+ - [ ] Code review passed
@@ -0,0 +1,58 @@
1
+ # Plan Template Usage Guide
2
+
3
+ ## Template Selection
4
+
5
+ ### Feature Implementation Template
6
+ **Use when**: Adding new functionality, endpoints, services, or modules
7
+ **File**: `feature-implementation-template.md`
8
+ **Size**: Medium to large scope changes
9
+
10
+ ### Bug Fix Template
11
+ **Use when**: Fixing specific issues, errors, or broken functionality
12
+ **File**: `bug-fix-template.md`
13
+ **Size**: Small to medium scope changes
14
+
15
+ ### Refactoring Template
16
+ **Use when**: Improving code structure, performance, or maintainability without changing functionality
17
+ **File**: `refactor-template.md`
18
+ **Size**: Medium to large scope changes
19
+
20
+ ## Context Management Best Practices
21
+
22
+ ### Keep Plans Focused
23
+ - **Executive Summary**: Max 3 sentences
24
+ - **Context Links**: Reference files, don't include full content
25
+ - **Tasks**: Max 10 per phase
26
+ - **Context Tokens**: Target <200 words for summaries
27
+
28
+ ### Template Adaptation
29
+ 1. Copy the appropriate template to `plans/YYMMDD-feature-name-plan.md`
30
+ 2. Replace bracketed placeholders with actual content
31
+ 3. Remove sections not relevant to your specific use case
32
+ 4. Keep the core structure intact for consistency
33
+
34
+ ### Cross-References Instead of Duplication
35
+ - Link to existing documentation in `./docs/`
36
+ - Reference other plans without copying content
37
+ - Use file paths instead of code blocks where possible
38
+ - Focus on "what" and "why", not detailed "how"
39
+
40
+ ## Quality Checklist
41
+
42
+ Before finalizing any plan:
43
+ - [ ] Executive summary is clear and concise
44
+ - [ ] Tasks are specific and actionable
45
+ - [ ] File paths are included for implementation tasks
46
+ - [ ] Success criteria are measurable
47
+ - [ ] Context links are used instead of full content
48
+ - [ ] TODO checklist is complete and realistic
49
+
50
+ ## Context Refresh Triggers
51
+
52
+ Use these templates when:
53
+ - Starting a new development phase
54
+ - Switching between different types of work (feature → bugfix)
55
+ - After major context accumulation (>8000 tokens)
56
+ - When agent handoffs occur
57
+
58
+ This ensures each plan starts with fresh, focused context optimized for the specific task type.
@@ -29,7 +29,6 @@ module Conversant
29
29
 
30
30
  # HTTP client module
31
31
  module HttpClient
32
- LOGIN_URL: String
33
32
  PORTAL_SESSION_REDIS_KEY: String
34
33
  SSO_GW_SESSION2_REDIS_KEY: String
35
34
 
@@ -48,6 +47,7 @@ module Conversant
48
47
  def update_cookie_jar: (untyped response) -> void
49
48
  def parse_cookie_to_jar: (String cookie_string) -> void
50
49
  def extract_form_action: (String html_body) -> String?
50
+ def login_url: () -> String
51
51
  def configuration: () -> Conversant::Configuration
52
52
  def redis: () -> untyped
53
53
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conversant
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.19
4
+ version: 1.0.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tho Nguyen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-06 00:00:00.000000000 Z
11
+ date: 2026-07-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.14'
75
+ version: 0.14.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.14'
82
+ version: 0.14.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -164,7 +164,8 @@ dependencies:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0.9'
167
- description: A Ruby gem providing a clean interface to interact with Conversant/SwiftFederation
167
+ description: |
168
+ A Ruby gem providing a clean interface to interact with Conversant/SwiftFederation
168
169
  CDN services including Portal, CDN, and LMS APIs with V3 authentication support.
169
170
  Features zero-configuration setup, automatic ENV detection, and seamless integration
170
171
  with existing Conversant implementations.
@@ -228,6 +229,10 @@ files:
228
229
  - lib/conversant/v3/services/vms/partner/business.rb
229
230
  - lib/conversant/v3/services/vms/transcoding.rb
230
231
  - lib/conversant/version.rb
232
+ - plans/templates/bug-fix-template.md
233
+ - plans/templates/feature-implementation-template.md
234
+ - plans/templates/refactor-template.md
235
+ - plans/templates/template-usage-guide.md
231
236
  - publish.sh
232
237
  - sig/conversant.rbs
233
238
  - sig/conversant/v3.rbs
@@ -260,7 +265,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
260
265
  - !ruby/object:Gem::Version
261
266
  version: '0'
262
267
  requirements: []
263
- rubygems_version: 3.2.3
268
+ rubygems_version: 3.5.10
264
269
  signing_key:
265
270
  specification_version: 4
266
271
  summary: Ruby client for Conversant CDN API (V3)