conversant 1.0.16

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +39 -0
  3. data/.gitignore +52 -0
  4. data/.gitlab-ci.yml +108 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +16 -0
  7. data/.yardopts +7 -0
  8. data/CHANGELOG.md +487 -0
  9. data/Gemfile +12 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +860 -0
  12. data/RELEASE.md +726 -0
  13. data/Rakefile +21 -0
  14. data/conversant.gemspec +49 -0
  15. data/examples/inheritance_integration.rb +348 -0
  16. data/examples/rails_initializer.rb +69 -0
  17. data/lib/conversant/configuration.rb +132 -0
  18. data/lib/conversant/v3/base.rb +47 -0
  19. data/lib/conversant/v3/http_client.rb +456 -0
  20. data/lib/conversant/v3/mixins/authentication.rb +221 -0
  21. data/lib/conversant/v3/services/authorization.rb +194 -0
  22. data/lib/conversant/v3/services/cdn/analytics.rb +483 -0
  23. data/lib/conversant/v3/services/cdn/audit.rb +71 -0
  24. data/lib/conversant/v3/services/cdn/business.rb +122 -0
  25. data/lib/conversant/v3/services/cdn/certificate.rb +180 -0
  26. data/lib/conversant/v3/services/cdn/dashboard.rb +109 -0
  27. data/lib/conversant/v3/services/cdn/domain.rb +223 -0
  28. data/lib/conversant/v3/services/cdn/monitoring.rb +65 -0
  29. data/lib/conversant/v3/services/cdn/partner/analytics.rb +233 -0
  30. data/lib/conversant/v3/services/cdn/partner.rb +60 -0
  31. data/lib/conversant/v3/services/cdn.rb +221 -0
  32. data/lib/conversant/v3/services/lms/dashboard.rb +99 -0
  33. data/lib/conversant/v3/services/lms/domain.rb +108 -0
  34. data/lib/conversant/v3/services/lms/job.rb +211 -0
  35. data/lib/conversant/v3/services/lms/partner/analytics.rb +266 -0
  36. data/lib/conversant/v3/services/lms/partner/business.rb +151 -0
  37. data/lib/conversant/v3/services/lms/partner/report.rb +170 -0
  38. data/lib/conversant/v3/services/lms/partner.rb +58 -0
  39. data/lib/conversant/v3/services/lms/preset.rb +57 -0
  40. data/lib/conversant/v3/services/lms.rb +173 -0
  41. data/lib/conversant/v3/services/oss/partner/analytics.rb +105 -0
  42. data/lib/conversant/v3/services/oss/partner.rb +48 -0
  43. data/lib/conversant/v3/services/oss.rb +128 -0
  44. data/lib/conversant/v3/services/portal/dashboard.rb +114 -0
  45. data/lib/conversant/v3/services/portal.rb +219 -0
  46. data/lib/conversant/v3/services/vms/analytics.rb +114 -0
  47. data/lib/conversant/v3/services/vms/business.rb +190 -0
  48. data/lib/conversant/v3/services/vms/partner/analytics.rb +133 -0
  49. data/lib/conversant/v3/services/vms/partner/business.rb +90 -0
  50. data/lib/conversant/v3/services/vms/partner.rb +57 -0
  51. data/lib/conversant/v3/services/vms/transcoding.rb +184 -0
  52. data/lib/conversant/v3/services/vms.rb +166 -0
  53. data/lib/conversant/v3.rb +36 -0
  54. data/lib/conversant/version.rb +5 -0
  55. data/lib/conversant.rb +108 -0
  56. data/publish.sh +107 -0
  57. data/sig/conversant/v3/services/authorization.rbs +34 -0
  58. data/sig/conversant/v3/services/cdn.rbs +123 -0
  59. data/sig/conversant/v3/services/lms.rbs +80 -0
  60. data/sig/conversant/v3/services/portal.rbs +22 -0
  61. data/sig/conversant/v3/services/vms.rbs +64 -0
  62. data/sig/conversant/v3.rbs +85 -0
  63. data/sig/conversant.rbs +37 -0
  64. metadata +267 -0
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec rubocop]
11
+
12
+ desc 'Run tests and linting'
13
+ task test: %i[spec rubocop]
14
+
15
+ desc 'Open a console with the gem loaded'
16
+ task :console do
17
+ require 'bundler/setup'
18
+ require 'conversant'
19
+ require 'pry'
20
+ Pry.start
21
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/conversant/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'conversant'
7
+ spec.version = Conversant::VERSION
8
+ spec.authors = ['Tho Nguyen']
9
+ spec.email = ['tho.nguyen@vnetwork.vn']
10
+
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.'
13
+ spec.homepage = 'https://gitlab.vnetwork.dev/gems/conversant'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.7.0'
16
+
17
+ spec.metadata = {
18
+ 'homepage_uri' => spec.homepage,
19
+ 'source_code_uri' => "#{spec.homepage}/-/tree/main",
20
+ 'changelog_uri' => "#{spec.homepage}/-/blob/main/CHANGELOG.md",
21
+ 'bug_tracker_uri' => "#{spec.homepage}/-/issues",
22
+ 'documentation_uri' => 'https://rubydoc.info/gems/conversant'
23
+ }
24
+
25
+ # Specify which files should be added to the gem when it is released.
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ f.match(%r{\A(?:test|spec|features)/})
29
+ end
30
+ end
31
+ spec.bindir = 'exe'
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ['lib']
34
+
35
+ # Runtime dependencies
36
+ spec.add_runtime_dependency 'rest-client', '~> 2.1'
37
+ spec.add_runtime_dependency 'redis', '~> 5.0'
38
+ spec.add_runtime_dependency 'json', '~> 2.6'
39
+
40
+ # Development dependencies
41
+ spec.add_development_dependency 'bundler', '~> 2.0'
42
+ spec.add_development_dependency 'rake', '~> 13.0'
43
+ spec.add_development_dependency 'rspec', '~> 3.12'
44
+ spec.add_development_dependency 'rubocop', '~> 1.50'
45
+ spec.add_development_dependency 'webmock', '~> 3.18'
46
+ spec.add_development_dependency 'vcr', '~> 6.1'
47
+ spec.add_development_dependency 'pry', '~> 0.14'
48
+ spec.add_development_dependency 'yard', '~> 0.9'
49
+ end
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'conversant'
6
+ require 'redis'
7
+ require 'logger'
8
+
9
+ # ==============================================================================
10
+ # STEP 1: Configure the Conversant gem
11
+ # ==============================================================================
12
+
13
+ # Option 1: Auto-configure from environment variables (recommended)
14
+ begin
15
+ # This will automatically load all settings from ENV variables:
16
+ # PORTAL_ROOT_HOSTNAME, PORTAL_SSO_HOSTNAME, PRIVATE_CDN_ENDPOINT,
17
+ # PRIVATE_LMS_ENDPOINT, SWIFTSERVE_IDENTIFIER_ID, SWIFTSERVE_IDENTIFIER_HASH,
18
+ # DEFAULT_UA, DEFAULT_CONTENT_TYPE, CONVERSANT_DEBUG, CONVERSANT_CACHE_TTL
19
+ Conversant.auto_configure!
20
+ puts "✓ Auto-configured from environment variables"
21
+ rescue Conversant::Error => e
22
+ puts "Auto-configuration failed: #{e.message}"
23
+
24
+ # Option 2: Manual configuration fallback
25
+ Conversant.configure do |config|
26
+ config.private_cdn_endpoint = ENV['PRIVATE_CDN_ENDPOINT'] || 'https://cdn.example.com'
27
+ config.private_lms_endpoint = ENV['PRIVATE_LMS_ENDPOINT'] || 'https://lms.example.com'
28
+ config.swiftserve_identifier_id = ENV['SWIFTSERVE_IDENTIFIER_ID'] || 'test_id'
29
+ config.swiftserve_identifier_hash = ENV['SWIFTSERVE_IDENTIFIER_HASH'] || 'test_hash'
30
+ config.redis = Redis.new(url: ENV['REDIS_URL'] || 'redis://localhost:6379')
31
+ end
32
+ end
33
+
34
+ # ==============================================================================
35
+ # STEP 2: Define your application's namespace structure
36
+ # ==============================================================================
37
+
38
+ module Utils
39
+ module Conversant
40
+ module V3
41
+ # Empty - will be populated with our classes
42
+ end
43
+ end
44
+ end
45
+
46
+ # ==============================================================================
47
+ # APPROACH 1: Direct Inheritance
48
+ # Your classes inherit from gem classes and extend functionality
49
+ # ==============================================================================
50
+
51
+ module Utils
52
+ module Conversant
53
+ module V3
54
+ # Portal Service - Inherits from gem and adds custom methods
55
+ class Portal < ::Conversant::V3::Services::Portal
56
+ # Add your custom business logic methods
57
+ def appliances_summary(gte = nil, lte = nil)
58
+ appliances_data = appliances(gte, lte)
59
+
60
+ {
61
+ total: appliances_data.length,
62
+ active: appliances_data.reject { |a| a['deleted'] }.length,
63
+ deleted: appliances_data.select { |a| a['deleted'] }.length,
64
+ by_pop: appliances_data.group_by { |a| a['pop'] }.transform_values(&:length),
65
+ total_volume: appliances_data.sum { |a| a['volume'] || 0 },
66
+ total_federation: appliances_data.sum { |a| a['federation'] || 0 }
67
+ }
68
+ end
69
+
70
+ # Override parent method if needed
71
+ def appliances(gte = nil, lte = nil)
72
+ logger.info "#{self.class.name} - Fetching appliances for customer: #{customer_id}"
73
+ super # Call parent implementation
74
+ end
75
+ end
76
+
77
+ # CDN Service - Inherits from gem and adds custom analytics
78
+ class CDN < ::Conversant::V3::Services::CDN
79
+ # Add custom analytics aggregation
80
+ def daily_summary(date = Date.today)
81
+ start_time = date.strftime("%Y-%m-%dT00:00:00Z")
82
+ end_time = date.strftime("%Y-%m-%dT23:59:59Z")
83
+
84
+ {
85
+ date: date,
86
+ bandwidth: analytics.bandwidths({
87
+ startTime: start_time,
88
+ endTime: end_time,
89
+ interval: "1h"
90
+ }),
91
+ volumes: analytics.volumes({
92
+ startTime: start_time,
93
+ endTime: end_time
94
+ }),
95
+ monitoring_spots: monitoring.spots({
96
+ startTime: start_time,
97
+ endTime: end_time
98
+ })
99
+ }
100
+ end
101
+
102
+ # Custom method for weekly reports
103
+ def weekly_report(start_date = Date.today - 7)
104
+ (0..6).map { |i| daily_summary(start_date + i) }
105
+ end
106
+ end
107
+
108
+ # LMS Service - Inherits from gem and adds stream management
109
+ class LMS < ::Conversant::V3::Services::LMS
110
+ # Add stream filtering and grouping
111
+ def active_streams_by_status
112
+ streams = job.where(limit: 1000)
113
+
114
+ return {} unless streams
115
+
116
+ streams.group_by { |s| s[:status] }.transform_values do |stream_list|
117
+ {
118
+ count: stream_list.length,
119
+ streams: stream_list.map { |s|
120
+ {
121
+ id: s[:id],
122
+ name: s[:name],
123
+ encoder: s[:encoder],
124
+ started_at: s[:started_at]
125
+ }
126
+ }
127
+ }
128
+ end
129
+ end
130
+
131
+ # Add business-specific method
132
+ def find_stream_by_name(stream_name)
133
+ streams = job.where(limit: 1000)
134
+ streams&.find { |s| s[:name] == stream_name }
135
+ end
136
+
137
+ # Monitor GPU usage
138
+ def gpu_utilization
139
+ streams = job.where(status: 'streaming')
140
+
141
+ return nil unless streams
142
+
143
+ total_streams = streams.length
144
+ gpu_streams = streams.select { |s| s[:gpu] }.length
145
+
146
+ {
147
+ total_streams: total_streams,
148
+ gpu_enabled: gpu_streams,
149
+ cpu_only: total_streams - gpu_streams,
150
+ gpu_percentage: total_streams > 0 ? (gpu_streams.to_f / total_streams * 100).round(2) : 0
151
+ }
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ # ==============================================================================
159
+ # APPROACH 2: Mixin-Based (When you can't change inheritance)
160
+ # Use when you have existing classes that can't inherit from gem
161
+ # ==============================================================================
162
+
163
+ module Utils
164
+ module Conversant
165
+ module V3
166
+ # Existing class that includes mixin instead of inheriting
167
+ class LegacyPortal
168
+ include ::Conversant::V3::Mixins::Authentication
169
+ use_conversant_auth!
170
+
171
+ def initialize(customer_id, type = 2)
172
+ @customer_id = customer_id
173
+ @type = type
174
+ initialize_conversant_auth(customer_id, type)
175
+ end
176
+
177
+ # Use gem's authentication for your custom methods
178
+ def get_appliance_metrics
179
+ headers = portal_authorized_headers
180
+
181
+ # Your custom API call using gem's auth
182
+ response = RestClient.post(
183
+ "#{configuration.portal_endpoint}/custom/metrics",
184
+ { customerId: @customer_id }.to_json,
185
+ headers
186
+ )
187
+
188
+ JSON.parse(response.body)
189
+ rescue => e
190
+ logger.error "Failed to get metrics: #{e.message}"
191
+ nil
192
+ end
193
+
194
+ private
195
+
196
+ def configuration
197
+ ::Conversant.configuration
198
+ end
199
+
200
+ def logger
201
+ configuration.logger
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ # ==============================================================================
209
+ # APPROACH 3: Composition (Wrapper Pattern)
210
+ # Wrap gem classes when you need more control
211
+ # ==============================================================================
212
+
213
+ module Utils
214
+ module Conversant
215
+ module V3
216
+ class PortalWrapper
217
+ attr_reader :portal_client, :customer_id
218
+
219
+ def initialize(customer_id, type = 2)
220
+ @customer_id = customer_id
221
+ @portal_client = ::Conversant::V3.portal(customer_id, type)
222
+ end
223
+
224
+ # Delegate to gem client
225
+ def appliances(*args)
226
+ portal_client.appliances(*args)
227
+ end
228
+
229
+ # Add your business logic
230
+ def appliances_with_cache(gte = nil, lte = nil)
231
+ cache_key = "portal_appliances_#{customer_id}_#{gte}_#{lte}"
232
+
233
+ cached = read_cache(cache_key)
234
+ return cached if cached
235
+
236
+ data = portal_client.appliances(gte, lte)
237
+ write_cache(cache_key, data, ttl: 3600)
238
+
239
+ data
240
+ end
241
+
242
+ private
243
+
244
+ def read_cache(key)
245
+ # Your caching logic
246
+ nil
247
+ end
248
+
249
+ def write_cache(key, data, ttl:)
250
+ # Your caching logic
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
256
+
257
+ # ==============================================================================
258
+ # USAGE EXAMPLES
259
+ # ==============================================================================
260
+
261
+ def demo_inheritance_approach
262
+ puts "\n" + "="*60
263
+ puts "INHERITANCE APPROACH - Using Utils::Conversant::V3 classes"
264
+ puts "="*60
265
+
266
+ customer_id = ENV['CUSTOMER_ID'] || '12345'
267
+
268
+ # Use your inherited Portal class
269
+ portal = Utils::Conversant::V3::Portal.new(customer_id)
270
+
271
+ puts "\n--- Portal Operations ---"
272
+ summary = portal.appliances_summary
273
+ puts "Appliances Summary:"
274
+ puts " Total: #{summary[:total]}"
275
+ puts " Active: #{summary[:active]}"
276
+ puts " By POP: #{summary[:by_pop]}"
277
+ puts " Total Volume: #{summary[:total_volume]}"
278
+
279
+ # Use your inherited CDN class
280
+ cdn = Utils::Conversant::V3::CDN.new(customer_id)
281
+
282
+ puts "\n--- CDN Operations ---"
283
+ daily = cdn.daily_summary
284
+ puts "Today's CDN Summary:"
285
+ puts " Date: #{daily[:date]}"
286
+ puts " Monitoring Spots: #{daily[:monitoring_spots]&.length || 0}"
287
+
288
+ # Use your inherited LMS class
289
+ lms = Utils::Conversant::V3::LMS.new(customer_id)
290
+
291
+ puts "\n--- LMS Operations ---"
292
+ gpu_stats = lms.gpu_utilization
293
+ if gpu_stats
294
+ puts "GPU Utilization:"
295
+ puts " Total Streams: #{gpu_stats[:total_streams]}"
296
+ puts " GPU Enabled: #{gpu_stats[:gpu_enabled]} (#{gpu_stats[:gpu_percentage]}%)"
297
+ end
298
+
299
+ streams_by_status = lms.active_streams_by_status
300
+ puts "Streams by Status:"
301
+ streams_by_status.each do |status, info|
302
+ puts " #{status}: #{info[:count]} streams"
303
+ end
304
+ end
305
+
306
+ def demo_mixin_approach
307
+ puts "\n" + "="*60
308
+ puts "MIXIN APPROACH - Using Authentication Mixin"
309
+ puts "="*60
310
+
311
+ customer_id = ENV['CUSTOMER_ID'] || '12345'
312
+
313
+ legacy = Utils::Conversant::V3::LegacyPortal.new(customer_id)
314
+ metrics = legacy.get_appliance_metrics
315
+ puts "Custom metrics retrieved: #{metrics ? 'Success' : 'Failed'}"
316
+ end
317
+
318
+ def demo_composition_approach
319
+ puts "\n" + "="*60
320
+ puts "COMPOSITION APPROACH - Using Wrapper Pattern"
321
+ puts "="*60
322
+
323
+ customer_id = ENV['CUSTOMER_ID'] || '12345'
324
+
325
+ wrapper = Utils::Conversant::V3::PortalWrapper.new(customer_id)
326
+ appliances = wrapper.appliances_with_cache
327
+ puts "Appliances via wrapper: #{appliances&.length || 0} items"
328
+ end
329
+
330
+ # ==============================================================================
331
+ # RUN THE EXAMPLES
332
+ # ==============================================================================
333
+
334
+ if __FILE__ == $0
335
+ begin
336
+ # Run all three approaches
337
+ demo_inheritance_approach
338
+ demo_mixin_approach
339
+ demo_composition_approach
340
+
341
+ puts "\n" + "="*60
342
+ puts "All examples completed successfully!"
343
+ puts "="*60
344
+ rescue => e
345
+ puts "\nError: #{e.message}"
346
+ puts e.backtrace.first(5)
347
+ end
348
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # config/initializers/conversant.rb
4
+ #
5
+ # Rails initializer for Conversant gem
6
+ # This file auto-configures the gem using your existing environment variables
7
+
8
+ require 'conversant'
9
+
10
+ # Auto-configure from ENV variables
11
+ begin
12
+ Conversant.auto_configure!
13
+
14
+ Rails.logger.info "✓ Conversant gem configured successfully"
15
+ Rails.logger.info " Portal: #{Conversant.configuration.portal_endpoint}"
16
+ Rails.logger.info " CDN: #{Conversant.configuration.private_cdn_endpoint}"
17
+ Rails.logger.info " LMS: #{Conversant.configuration.private_lms_endpoint}"
18
+ Rails.logger.info " Debug: #{Conversant.configuration.debug_mode}"
19
+ Rails.logger.info " Cache TTL: #{Conversant.configuration.cache_ttl}s"
20
+
21
+ rescue Conversant::Error => e
22
+ Rails.logger.error "Conversant configuration failed: #{e.message}"
23
+
24
+ # Optional: Raise error in production to prevent startup with bad config
25
+ raise if Rails.env.production?
26
+
27
+ # Optional: Use fallback configuration in development
28
+ if Rails.env.development?
29
+ Conversant.configure do |config|
30
+ config.private_cdn_endpoint = 'https://cdn.dev.example.com'
31
+ config.private_lms_endpoint = 'https://lms.dev.example.com'
32
+ config.swiftserve_identifier_id = 'dev_id'
33
+ config.swiftserve_identifier_hash = 'dev_hash'
34
+ config.redis = $redis || Redis.new
35
+ end
36
+ Rails.logger.warn "Using development fallback configuration"
37
+ end
38
+ end
39
+
40
+ # Optional: Add convenience methods to ApplicationController or as a concern
41
+ module ConversantHelper
42
+ extend ActiveSupport::Concern
43
+
44
+ included do
45
+ helper_method :conversant_portal, :conversant_cdn, :conversant_lms if respond_to?(:helper_method)
46
+ end
47
+
48
+ private
49
+
50
+ def conversant_portal(customer_id)
51
+ @conversant_portal ||= {}
52
+ @conversant_portal[customer_id] ||= Conversant::V3.portal(customer_id)
53
+ end
54
+
55
+ def conversant_cdn(customer_id)
56
+ @conversant_cdn ||= {}
57
+ @conversant_cdn[customer_id] ||= Conversant::V3.cdn(customer_id)
58
+ end
59
+
60
+ def conversant_lms(customer_id)
61
+ @conversant_lms ||= {}
62
+ @conversant_lms[customer_id] ||= Conversant::V3.lms(customer_id)
63
+ end
64
+ end
65
+
66
+ # Optional: Include in ApplicationController
67
+ # class ApplicationController < ActionController::Base
68
+ # include ConversantHelper
69
+ # end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Conversant
4
+ class Configuration
5
+ attr_accessor :portal_root_hostname,
6
+ :portal_sso_hostname,
7
+ :private_cdn_endpoint,
8
+ :private_lms_endpoint,
9
+ :private_vms_endpoint,
10
+ :private_oss_endpoint,
11
+ :private_portal_endpoint,
12
+ :private_sso_endpoint,
13
+ :conversant_cdn_api_url,
14
+ :swiftserve_identifier_id,
15
+ :swiftserve_identifier_hash,
16
+ :default_ua,
17
+ :default_content_type,
18
+ :redis,
19
+ :logger,
20
+ :debug_mode,
21
+ :verify_ssl,
22
+ :cache_ttl
23
+
24
+ def initialize
25
+ # All configuration automatically uses ENV variables as defaults
26
+ # Users only need to override if they want different values
27
+
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'
31
+
32
+ # API Endpoints - defaults from ENV (matching existing CONVERSANT setup)
33
+ @private_cdn_endpoint = ENV['PRIVATE_CDN_ENDPOINT']
34
+ @private_lms_endpoint = ENV['PRIVATE_LMS_ENDPOINT']
35
+ @private_vms_endpoint = ENV['PRIVATE_VMS_ENDPOINT']
36
+ @private_oss_endpoint = ENV['PRIVATE_OSS_ENDPOINT']
37
+ @private_portal_endpoint = ENV['PRIVATE_PORTAL_ENDPOINT'] || "https://#{@portal_root_hostname}"
38
+ @private_sso_endpoint = ENV['PRIVATE_SSO_ENDPOINT'] || "https://#{@portal_sso_hostname}"
39
+
40
+ # Authentication - defaults from ENV
41
+ @swiftserve_identifier_id = ENV['SWIFTSERVE_IDENTIFIER_ID']
42
+ @swiftserve_identifier_hash = ENV['SWIFTSERVE_IDENTIFIER_HASH']
43
+
44
+ # HTTP settings - defaults from ENV
45
+ @default_ua = ENV['DEFAULT_UA'] || 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
46
+ @default_content_type = ENV['DEFAULT_CONTENT_TYPE'] || 'application/json'
47
+
48
+ # Additional CDN API URL if needed
49
+ @conversant_cdn_api_url = ENV['CONVERSANT_CDN_API_URL']
50
+
51
+ # Runtime settings
52
+ @logger = Logger.new($stdout)
53
+ @debug_mode = ENV['CONVERSANT_DEBUG'] == 'true' || ENV['DEBUG'] == 'true'
54
+ @verify_ssl = ENV['RAILS_ENV'] == 'production'
55
+ @cache_ttl = (ENV['CONVERSANT_CACHE_TTL'] || 1200).to_i
56
+
57
+ # Redis - auto-detect from global $redis or ENV
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
67
+ end
68
+
69
+ def portal_endpoint
70
+ @private_portal_endpoint || "https://#{portal_root_hostname}"
71
+ end
72
+
73
+ def sso_endpoint
74
+ @private_sso_endpoint || "https://#{portal_sso_hostname}"
75
+ end
76
+
77
+ def validate!
78
+ required_fields = []
79
+
80
+ # Only require credentials if not auto-configured
81
+ unless auto_configured?
82
+ required_fields += [
83
+ :swiftserve_identifier_id,
84
+ :swiftserve_identifier_hash
85
+ ]
86
+ end
87
+
88
+ # Always require endpoints
89
+ required_fields += [
90
+ :private_cdn_endpoint,
91
+ :private_lms_endpoint,
92
+ :private_vms_endpoint,
93
+ :redis
94
+ ]
95
+
96
+ missing_fields = required_fields.select { |field| send(field).nil? || send(field).to_s.empty? }
97
+
98
+ if missing_fields.any?
99
+ raise Error, "Missing required configuration: #{missing_fields.join(', ')}\n" \
100
+ "Please set the following environment variables: #{missing_fields.map { |f| env_var_name(f) }.join(', ')}"
101
+ end
102
+
103
+ true
104
+ end
105
+
106
+ # Check if configuration is loaded from environment
107
+ def auto_configured?
108
+ !@swiftserve_identifier_id.nil? && !@swiftserve_identifier_hash.nil?
109
+ end
110
+
111
+ # Helper to map config field to ENV variable name
112
+ def env_var_name(field)
113
+ case field
114
+ when :swiftserve_identifier_id then 'SWIFTSERVE_IDENTIFIER_ID'
115
+ when :swiftserve_identifier_hash then 'SWIFTSERVE_IDENTIFIER_HASH'
116
+ when :private_cdn_endpoint then 'PRIVATE_CDN_ENDPOINT'
117
+ when :private_lms_endpoint then 'PRIVATE_LMS_ENDPOINT'
118
+ when :private_vms_endpoint then 'PRIVATE_VMS_ENDPOINT'
119
+ when :portal_root_hostname then 'PORTAL_ROOT_HOSTNAME'
120
+ when :portal_sso_hostname then 'PORTAL_SSO_HOSTNAME'
121
+ else field.to_s.upcase
122
+ end
123
+ end
124
+
125
+ # Load configuration from environment and validate
126
+ def self.from_env
127
+ config = new
128
+ config.validate!
129
+ config
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Conversant
4
+ module V3
5
+ class Base
6
+ include HttpClient
7
+
8
+ attr_reader :customer_id, :type
9
+
10
+ def initialize(customer_id, type = 2)
11
+ @customer_id = customer_id
12
+ @type = type
13
+ validate_configuration!
14
+ end
15
+
16
+ protected
17
+
18
+ def validate_configuration!
19
+ Conversant.configuration.validate!
20
+ end
21
+
22
+ def configuration
23
+ Conversant.configuration
24
+ end
25
+
26
+ def redis
27
+ configuration.redis
28
+ end
29
+
30
+ def portal_endpoint
31
+ configuration.portal_endpoint
32
+ end
33
+
34
+ def sso_endpoint
35
+ configuration.sso_endpoint
36
+ end
37
+
38
+ def identifier
39
+ @identifier ||= self.class.name.split('::').map(&:upcase).join('-')
40
+ end
41
+
42
+ def logger
43
+ configuration.logger
44
+ end
45
+ end
46
+ end
47
+ end