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.
- checksums.yaml +7 -0
- data/.env.example +39 -0
- data/.gitignore +52 -0
- data/.gitlab-ci.yml +108 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +487 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +860 -0
- data/RELEASE.md +726 -0
- data/Rakefile +21 -0
- data/conversant.gemspec +49 -0
- data/examples/inheritance_integration.rb +348 -0
- data/examples/rails_initializer.rb +69 -0
- data/lib/conversant/configuration.rb +132 -0
- data/lib/conversant/v3/base.rb +47 -0
- data/lib/conversant/v3/http_client.rb +456 -0
- data/lib/conversant/v3/mixins/authentication.rb +221 -0
- data/lib/conversant/v3/services/authorization.rb +194 -0
- data/lib/conversant/v3/services/cdn/analytics.rb +483 -0
- data/lib/conversant/v3/services/cdn/audit.rb +71 -0
- data/lib/conversant/v3/services/cdn/business.rb +122 -0
- data/lib/conversant/v3/services/cdn/certificate.rb +180 -0
- data/lib/conversant/v3/services/cdn/dashboard.rb +109 -0
- data/lib/conversant/v3/services/cdn/domain.rb +223 -0
- data/lib/conversant/v3/services/cdn/monitoring.rb +65 -0
- data/lib/conversant/v3/services/cdn/partner/analytics.rb +233 -0
- data/lib/conversant/v3/services/cdn/partner.rb +60 -0
- data/lib/conversant/v3/services/cdn.rb +221 -0
- data/lib/conversant/v3/services/lms/dashboard.rb +99 -0
- data/lib/conversant/v3/services/lms/domain.rb +108 -0
- data/lib/conversant/v3/services/lms/job.rb +211 -0
- data/lib/conversant/v3/services/lms/partner/analytics.rb +266 -0
- data/lib/conversant/v3/services/lms/partner/business.rb +151 -0
- data/lib/conversant/v3/services/lms/partner/report.rb +170 -0
- data/lib/conversant/v3/services/lms/partner.rb +58 -0
- data/lib/conversant/v3/services/lms/preset.rb +57 -0
- data/lib/conversant/v3/services/lms.rb +173 -0
- data/lib/conversant/v3/services/oss/partner/analytics.rb +105 -0
- data/lib/conversant/v3/services/oss/partner.rb +48 -0
- data/lib/conversant/v3/services/oss.rb +128 -0
- data/lib/conversant/v3/services/portal/dashboard.rb +114 -0
- data/lib/conversant/v3/services/portal.rb +219 -0
- data/lib/conversant/v3/services/vms/analytics.rb +114 -0
- data/lib/conversant/v3/services/vms/business.rb +190 -0
- data/lib/conversant/v3/services/vms/partner/analytics.rb +133 -0
- data/lib/conversant/v3/services/vms/partner/business.rb +90 -0
- data/lib/conversant/v3/services/vms/partner.rb +57 -0
- data/lib/conversant/v3/services/vms/transcoding.rb +184 -0
- data/lib/conversant/v3/services/vms.rb +166 -0
- data/lib/conversant/v3.rb +36 -0
- data/lib/conversant/version.rb +5 -0
- data/lib/conversant.rb +108 -0
- data/publish.sh +107 -0
- data/sig/conversant/v3/services/authorization.rbs +34 -0
- data/sig/conversant/v3/services/cdn.rbs +123 -0
- data/sig/conversant/v3/services/lms.rbs +80 -0
- data/sig/conversant/v3/services/portal.rbs +22 -0
- data/sig/conversant/v3/services/vms.rbs +64 -0
- data/sig/conversant/v3.rbs +85 -0
- data/sig/conversant.rbs +37 -0
- 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
|
data/conversant.gemspec
ADDED
|
@@ -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
|