licensechain_ruby_sdk 1.0.1

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.
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LicenseChain
4
+ class LicenseValidator
5
+ attr_reader :client
6
+
7
+ def initialize(api_key: nil, base_url: nil, timeout: nil, retry_attempts: nil)
8
+ @client = Client.new(
9
+ api_key: api_key,
10
+ base_url: base_url,
11
+ timeout: timeout,
12
+ retry_attempts: retry_attempts
13
+ )
14
+ end
15
+
16
+ # Validate a license key
17
+ def validate_license(license_key, app_id: nil)
18
+ response = @client.validate_license(license_key, app_id: app_id)
19
+
20
+ ValidationResult.new(
21
+ valid: response["valid"],
22
+ license: response["license"],
23
+ user: response["user"],
24
+ app: response["app"],
25
+ expires_at: response["expires_at"],
26
+ metadata: response["metadata"],
27
+ error: response["error"]
28
+ )
29
+ end
30
+
31
+ # Check if license is valid without full validation
32
+ def valid?(license_key, app_id: nil)
33
+ result = validate_license(license_key, app_id: app_id)
34
+ result.valid?
35
+ end
36
+
37
+ # Get license information
38
+ def get_license_info(license_key, app_id: nil)
39
+ result = validate_license(license_key, app_id: app_id)
40
+ result.license
41
+ end
42
+
43
+ # Check if license is expired
44
+ def expired?(license_key, app_id: nil)
45
+ result = validate_license(license_key, app_id: app_id)
46
+ return true unless result.valid?
47
+
48
+ return false unless result.expires_at
49
+
50
+ Time.parse(result.expires_at) < Time.now
51
+ end
52
+
53
+ # Get days until expiration
54
+ def days_until_expiration(license_key, app_id: nil)
55
+ result = validate_license(license_key, app_id: app_id)
56
+ return nil unless result.valid? && result.expires_at
57
+
58
+ expires_at = Time.parse(result.expires_at)
59
+ ((expires_at - Time.now) / 1.day).ceil
60
+ end
61
+
62
+ # Batch validate multiple licenses
63
+ def validate_licenses(license_keys, app_id: nil)
64
+ license_keys.map do |license_key|
65
+ validate_license(license_key, app_id: app_id)
66
+ end
67
+ end
68
+
69
+ # Validate with custom validation rules
70
+ def validate_with_rules(license_key, app_id: nil, rules: {})
71
+ result = validate_license(license_key, app_id: app_id)
72
+ return result unless result.valid?
73
+
74
+ # Apply custom validation rules
75
+ if rules[:max_usage] && result.license&.dig("usage_count").to_i > rules[:max_usage]
76
+ result.instance_variable_set(:@valid, false)
77
+ result.instance_variable_set(:@error, "Usage limit exceeded")
78
+ end
79
+
80
+ if rules[:allowed_features] && result.license&.dig("features")
81
+ allowed_features = rules[:allowed_features]
82
+ license_features = result.license["features"] || []
83
+ invalid_features = license_features - allowed_features
84
+
85
+ if invalid_features.any?
86
+ result.instance_variable_set(:@valid, false)
87
+ result.instance_variable_set(:@error, "Invalid features: #{invalid_features.join(', ')}")
88
+ end
89
+ end
90
+
91
+ result
92
+ end
93
+ end
94
+
95
+ # Result object for license validation
96
+ class ValidationResult
97
+ attr_reader :valid, :license, :user, :app, :expires_at, :metadata, :error
98
+
99
+ def initialize(valid:, license: nil, user: nil, app: nil, expires_at: nil, metadata: nil, error: nil)
100
+ @valid = valid
101
+ @license = license
102
+ @user = user
103
+ @app = app
104
+ @expires_at = expires_at
105
+ @metadata = metadata || {}
106
+ @error = error
107
+ end
108
+
109
+ def valid?
110
+ @valid
111
+ end
112
+
113
+ def invalid?
114
+ !@valid
115
+ end
116
+
117
+ def expired?
118
+ return false unless @expires_at
119
+ Time.parse(@expires_at) < Time.now
120
+ end
121
+
122
+ def user_email
123
+ @user&.dig("email")
124
+ end
125
+
126
+ def user_name
127
+ @user&.dig("name")
128
+ end
129
+
130
+ def app_name
131
+ @app&.dig("name")
132
+ end
133
+
134
+ def license_key
135
+ @license&.dig("key")
136
+ end
137
+
138
+ def license_id
139
+ @license&.dig("id")
140
+ end
141
+
142
+ def features
143
+ @license&.dig("features") || []
144
+ end
145
+
146
+ def usage_count
147
+ @license&.dig("usage_count") || 0
148
+ end
149
+
150
+ def created_at
151
+ @license&.dig("created_at")
152
+ end
153
+
154
+ def updated_at
155
+ @license&.dig("updated_at")
156
+ end
157
+
158
+ def to_h
159
+ {
160
+ valid: @valid,
161
+ license: @license,
162
+ user: @user,
163
+ app: @app,
164
+ expires_at: @expires_at,
165
+ metadata: @metadata,
166
+ error: @error
167
+ }
168
+ end
169
+
170
+ def to_json(*args)
171
+ to_h.to_json(*args)
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,354 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LicenseChain
4
+ # Base model class
5
+ class Model
6
+ include Enumerable
7
+
8
+ def initialize(attributes = {})
9
+ @attributes = attributes.dup
10
+ end
11
+
12
+ def [](key)
13
+ @attributes[key.to_s]
14
+ end
15
+
16
+ def []=(key, value)
17
+ @attributes[key.to_s] = value
18
+ end
19
+
20
+ def each(&block)
21
+ @attributes.each(&block)
22
+ end
23
+
24
+ def keys
25
+ @attributes.keys
26
+ end
27
+
28
+ def values
29
+ @attributes.values
30
+ end
31
+
32
+ def to_h
33
+ @attributes.dup
34
+ end
35
+
36
+ def to_json(*args)
37
+ @attributes.to_json(*args)
38
+ end
39
+
40
+ def inspect
41
+ "#<#{self.class.name}:#{object_id} #{@attributes.inspect}>"
42
+ end
43
+
44
+ def ==(other)
45
+ other.is_a?(self.class) && @attributes == other.instance_variable_get(:@attributes)
46
+ end
47
+
48
+ def method_missing(method_name, *args, &block)
49
+ if @attributes.key?(method_name.to_s)
50
+ @attributes[method_name.to_s]
51
+ elsif @attributes.key?(method_name.to_s.chomp("="))
52
+ @attributes[method_name.to_s.chomp("=")] = args.first
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ def respond_to_missing?(method_name, include_private = false)
59
+ @attributes.key?(method_name.to_s) || @attributes.key?(method_name.to_s.chomp("=")) || super
60
+ end
61
+ end
62
+
63
+ # User model
64
+ class User < Model
65
+ def id
66
+ self["id"]
67
+ end
68
+
69
+ def email
70
+ self["email"]
71
+ end
72
+
73
+ def name
74
+ self["name"]
75
+ end
76
+
77
+ def company
78
+ self["company"]
79
+ end
80
+
81
+ def created_at
82
+ Time.parse(self["created_at"]) if self["created_at"]
83
+ end
84
+
85
+ def updated_at
86
+ Time.parse(self["updated_at"]) if self["updated_at"]
87
+ end
88
+
89
+ def email_verified?
90
+ self["email_verified"] == true
91
+ end
92
+
93
+ def active?
94
+ self["status"] == "active"
95
+ end
96
+ end
97
+
98
+ # Application model
99
+ class Application < Model
100
+ def id
101
+ self["id"]
102
+ end
103
+
104
+ def name
105
+ self["name"]
106
+ end
107
+
108
+ def description
109
+ self["description"]
110
+ end
111
+
112
+ def api_key
113
+ self["api_key"]
114
+ end
115
+
116
+ def webhook_url
117
+ self["webhook_url"]
118
+ end
119
+
120
+ def allowed_origins
121
+ self["allowed_origins"] || []
122
+ end
123
+
124
+ def created_at
125
+ Time.parse(self["created_at"]) if self["created_at"]
126
+ end
127
+
128
+ def updated_at
129
+ Time.parse(self["updated_at"]) if self["updated_at"]
130
+ end
131
+
132
+ def active?
133
+ self["status"] == "active"
134
+ end
135
+
136
+ def license_count
137
+ self["license_count"] || 0
138
+ end
139
+ end
140
+
141
+ # License model
142
+ class License < Model
143
+ def id
144
+ self["id"]
145
+ end
146
+
147
+ def key
148
+ self["key"]
149
+ end
150
+
151
+ def app_id
152
+ self["app_id"]
153
+ end
154
+
155
+ def user_id
156
+ self["user_id"]
157
+ end
158
+
159
+ def user_email
160
+ self["user_email"]
161
+ end
162
+
163
+ def user_name
164
+ self["user_name"]
165
+ end
166
+
167
+ def status
168
+ self["status"]
169
+ end
170
+
171
+ def expires_at
172
+ Time.parse(self["expires_at"]) if self["expires_at"]
173
+ end
174
+
175
+ def created_at
176
+ Time.parse(self["created_at"]) if self["created_at"]
177
+ end
178
+
179
+ def updated_at
180
+ Time.parse(self["updated_at"]) if self["updated_at"]
181
+ end
182
+
183
+ def metadata
184
+ self["metadata"] || {}
185
+ end
186
+
187
+ def features
188
+ self["features"] || []
189
+ end
190
+
191
+ def usage_count
192
+ self["usage_count"] || 0
193
+ end
194
+
195
+ def active?
196
+ self["status"] == "active"
197
+ end
198
+
199
+ def expired?
200
+ return false unless expires_at
201
+ expires_at < Time.now
202
+ end
203
+
204
+ def revoked?
205
+ self["status"] == "revoked"
206
+ end
207
+
208
+ def days_until_expiration
209
+ return nil unless expires_at
210
+ ((expires_at - Time.now) / 1.day).ceil
211
+ end
212
+ end
213
+
214
+ # Webhook model
215
+ class Webhook < Model
216
+ def id
217
+ self["id"]
218
+ end
219
+
220
+ def app_id
221
+ self["app_id"]
222
+ end
223
+
224
+ def url
225
+ self["url"]
226
+ end
227
+
228
+ def events
229
+ self["events"] || []
230
+ end
231
+
232
+ def secret
233
+ self["secret"]
234
+ end
235
+
236
+ def active?
237
+ self["status"] == "active"
238
+ end
239
+
240
+ def created_at
241
+ Time.parse(self["created_at"]) if self["created_at"]
242
+ end
243
+
244
+ def updated_at
245
+ Time.parse(self["updated_at"]) if self["updated_at"]
246
+ end
247
+
248
+ def last_triggered_at
249
+ Time.parse(self["last_triggered_at"]) if self["last_triggered_at"]
250
+ end
251
+
252
+ def failure_count
253
+ self["failure_count"] || 0
254
+ end
255
+ end
256
+
257
+ # Analytics model
258
+ class Analytics < Model
259
+ def total_licenses
260
+ self["total_licenses"] || 0
261
+ end
262
+
263
+ def active_licenses
264
+ self["active_licenses"] || 0
265
+ end
266
+
267
+ def expired_licenses
268
+ self["expired_licenses"] || 0
269
+ end
270
+
271
+ def revoked_licenses
272
+ self["revoked_licenses"] || 0
273
+ end
274
+
275
+ def validations_today
276
+ self["validations_today"] || 0
277
+ end
278
+
279
+ def validations_this_week
280
+ self["validations_this_week"] || 0
281
+ end
282
+
283
+ def validations_this_month
284
+ self["validations_this_month"] || 0
285
+ end
286
+
287
+ def top_features
288
+ self["top_features"] || []
289
+ end
290
+
291
+ def usage_by_day
292
+ self["usage_by_day"] || []
293
+ end
294
+
295
+ def usage_by_week
296
+ self["usage_by_week"] || []
297
+ end
298
+
299
+ def usage_by_month
300
+ self["usage_by_month"] || []
301
+ end
302
+ end
303
+
304
+ # Paginated response model
305
+ class PaginatedResponse < Model
306
+ def data
307
+ self["data"] || []
308
+ end
309
+
310
+ def page
311
+ self["page"] || 1
312
+ end
313
+
314
+ def limit
315
+ self["limit"] || 20
316
+ end
317
+
318
+ def total
319
+ self["total"] || 0
320
+ end
321
+
322
+ def total_pages
323
+ self["total_pages"] || 1
324
+ end
325
+
326
+ def has_next_page?
327
+ page < total_pages
328
+ end
329
+
330
+ def has_previous_page?
331
+ page > 1
332
+ end
333
+
334
+ def next_page
335
+ has_next_page? ? page + 1 : nil
336
+ end
337
+
338
+ def previous_page
339
+ has_previous_page? ? page - 1 : nil
340
+ end
341
+
342
+ def each(&block)
343
+ data.each(&block)
344
+ end
345
+
346
+ def size
347
+ data.size
348
+ end
349
+
350
+ def empty?
351
+ data.empty?
352
+ end
353
+ end
354
+ end
@@ -0,0 +1,108 @@
1
+ module LicenseChain
2
+ module Services
3
+ class LicenseService
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+
8
+ def create(app_id, user_email, metadata = {})
9
+ Utils.validate_not_empty(app_id, 'app_id')
10
+ Utils.validate_not_empty(user_email, 'user_email')
11
+
12
+ data = {
13
+ appId: app_id,
14
+ plan: 'FREE',
15
+ issuedEmail: user_email,
16
+ metadata: Utils.sanitize_metadata(metadata)
17
+ }
18
+
19
+ response = @client.post("/apps/#{app_id}/licenses", data)
20
+ License.new(normalize_license_payload(response[:data] || response))
21
+ end
22
+
23
+ def get(license_id)
24
+ Utils.validate_not_empty(license_id, 'license_id')
25
+
26
+ response = @client.get("/licenses/#{license_id}")
27
+ License.new(normalize_license_payload(response[:data] || response))
28
+ end
29
+
30
+ def update(license_id, updates = {})
31
+ Utils.validate_not_empty(license_id, 'license_id')
32
+
33
+ response = @client.patch("/licenses/#{license_id}", Utils.sanitize_metadata(updates))
34
+ License.new(normalize_license_payload(response[:data] || response))
35
+ end
36
+
37
+ def revoke(license_id)
38
+ Utils.validate_not_empty(license_id, 'license_id')
39
+
40
+ @client.delete("/licenses/#{license_id}")
41
+ true
42
+ end
43
+
44
+ def validate(license_key, hwuid = nil)
45
+ Utils.validate_not_empty(license_key, 'license_key')
46
+ body = { key: license_key }
47
+ body[:hwuid] = hwuid.to_s.strip != '' ? hwuid.to_s.strip : Utils.default_hwuid
48
+ response = @client.post('/licenses/verify', body)
49
+ response[:valid]
50
+ end
51
+
52
+ # Full POST /licenses/verify body (valid, optional license_token, license_jwks_uri, etc.).
53
+ def verify_with_details(license_key, hwuid = nil)
54
+ Utils.validate_not_empty(license_key, 'license_key')
55
+ body = { key: license_key }
56
+ body[:hwuid] = hwuid.to_s.strip != '' ? hwuid.to_s.strip : Utils.default_hwuid
57
+ @client.post('/licenses/verify', body)
58
+ end
59
+
60
+ def list_user_licenses(user_id, page = 1, limit = 10)
61
+ Utils.validate_not_empty(user_id, 'user_id')
62
+ page, limit = Utils.validate_pagination(page, limit)
63
+
64
+ response = @client.get('/licenses', { page: page, limit: limit })
65
+ items = response[:data] || response[:licenses] || []
66
+ filtered = items.select do |license|
67
+ license[:issuedEmail] == user_id || license[:email] == user_id || license[:user_id] == user_id
68
+ end
69
+ {
70
+ data: filtered.map { |license| License.new(normalize_license_payload(license)) },
71
+ total: filtered.length,
72
+ page: page,
73
+ limit: limit
74
+ }
75
+ end
76
+
77
+ def stats
78
+ response = @client.get('/licenses/stats')
79
+ LicenseStats.new(response[:data] || response)
80
+ end
81
+
82
+ private
83
+
84
+ def validate_uuid(id, field_name)
85
+ Utils.validate_not_empty(id, field_name)
86
+ raise ValidationError, "Invalid #{field_name} format" unless Utils.validate_uuid(id)
87
+ end
88
+
89
+ def normalize_license_payload(payload)
90
+ {
91
+ id: payload[:id],
92
+ key: payload[:key] || payload[:licenseKey],
93
+ app_id: payload[:app_id] || payload[:appId] || '',
94
+ user_id: payload[:user_id],
95
+ user_email: payload[:user_email] || payload[:issuedEmail] || payload[:email] || '',
96
+ user_name: payload[:user_name] || payload[:issuedTo],
97
+ status: (payload[:status] || 'active').to_s.downcase,
98
+ expires_at: payload[:expires_at] || payload[:expiresAt],
99
+ created_at: payload[:created_at] || payload[:createdAt],
100
+ updated_at: payload[:updated_at] || payload[:updatedAt],
101
+ metadata: payload[:metadata] || {},
102
+ features: payload[:features] || [],
103
+ usage_count: payload[:usage_count] || 0
104
+ }
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,49 @@
1
+ module LicenseChain
2
+ module Services
3
+ class ProductService
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+
8
+ def create(name, description = nil, price = nil, currency = 'USD', metadata = {})
9
+ validate_required_params(name, price, currency)
10
+ raise ValidationError, 'Product endpoints are not available in API v1'
11
+ end
12
+
13
+ def get(product_id)
14
+ raise ValidationError, 'Product endpoints are not available in API v1'
15
+ end
16
+
17
+ def update(product_id, updates = {})
18
+ raise ValidationError, 'Product endpoints are not available in API v1'
19
+ end
20
+
21
+ def delete(product_id)
22
+ raise ValidationError, 'Product endpoints are not available in API v1'
23
+ end
24
+
25
+ def list(page = 1, limit = 10)
26
+ page, limit = Utils.validate_pagination(page, limit)
27
+ {
28
+ data: [],
29
+ total: 0,
30
+ page: page,
31
+ limit: limit
32
+ }
33
+ end
34
+
35
+ def stats
36
+ ProductStats.new({ total: 0, active: 0, revenue: 0 })
37
+ end
38
+
39
+ private
40
+
41
+ def validate_required_params(name, price, currency)
42
+ Utils.validate_not_empty(name, 'name')
43
+ Utils.validate_positive(price, 'price')
44
+ raise ValidationError, 'Invalid currency' unless Utils.validate_currency(currency)
45
+ end
46
+
47
+ end
48
+ end
49
+ end