apisonator 2.100.0

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 (173) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +317 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.base +65 -0
  5. data/Gemfile.lock +319 -0
  6. data/Gemfile.on_prem +1 -0
  7. data/Gemfile.on_prem.lock +297 -0
  8. data/LICENSE +202 -0
  9. data/NOTICE +15 -0
  10. data/README.md +230 -0
  11. data/Rakefile +287 -0
  12. data/apisonator.gemspec +47 -0
  13. data/app/api/api.rb +13 -0
  14. data/app/api/internal/alert_limits.rb +32 -0
  15. data/app/api/internal/application_keys.rb +49 -0
  16. data/app/api/internal/application_referrer_filters.rb +43 -0
  17. data/app/api/internal/applications.rb +77 -0
  18. data/app/api/internal/errors.rb +54 -0
  19. data/app/api/internal/events.rb +42 -0
  20. data/app/api/internal/internal.rb +104 -0
  21. data/app/api/internal/metrics.rb +40 -0
  22. data/app/api/internal/service_tokens.rb +46 -0
  23. data/app/api/internal/services.rb +58 -0
  24. data/app/api/internal/stats.rb +42 -0
  25. data/app/api/internal/usagelimits.rb +62 -0
  26. data/app/api/internal/utilization.rb +23 -0
  27. data/bin/3scale_backend +223 -0
  28. data/bin/3scale_backend_worker +26 -0
  29. data/config.ru +4 -0
  30. data/config/puma.rb +192 -0
  31. data/config/schedule.rb +9 -0
  32. data/ext/mkrf_conf.rb +64 -0
  33. data/lib/3scale/backend.rb +67 -0
  34. data/lib/3scale/backend/alert_limit.rb +56 -0
  35. data/lib/3scale/backend/alerts.rb +137 -0
  36. data/lib/3scale/backend/analytics/kinesis.rb +3 -0
  37. data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
  38. data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
  39. data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
  40. data/lib/3scale/backend/analytics/redshift.rb +3 -0
  41. data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
  42. data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
  43. data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
  44. data/lib/3scale/backend/application.rb +330 -0
  45. data/lib/3scale/backend/application_events.rb +76 -0
  46. data/lib/3scale/backend/background_job.rb +65 -0
  47. data/lib/3scale/backend/configurable.rb +20 -0
  48. data/lib/3scale/backend/configuration.rb +151 -0
  49. data/lib/3scale/backend/configuration/loader.rb +42 -0
  50. data/lib/3scale/backend/constants.rb +19 -0
  51. data/lib/3scale/backend/cors.rb +84 -0
  52. data/lib/3scale/backend/distributed_lock.rb +67 -0
  53. data/lib/3scale/backend/environment.rb +21 -0
  54. data/lib/3scale/backend/error_storage.rb +52 -0
  55. data/lib/3scale/backend/errors.rb +343 -0
  56. data/lib/3scale/backend/event_storage.rb +120 -0
  57. data/lib/3scale/backend/experiment.rb +84 -0
  58. data/lib/3scale/backend/extensions.rb +5 -0
  59. data/lib/3scale/backend/extensions/array.rb +19 -0
  60. data/lib/3scale/backend/extensions/hash.rb +26 -0
  61. data/lib/3scale/backend/extensions/nil_class.rb +13 -0
  62. data/lib/3scale/backend/extensions/redis.rb +44 -0
  63. data/lib/3scale/backend/extensions/string.rb +13 -0
  64. data/lib/3scale/backend/extensions/time.rb +110 -0
  65. data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
  66. data/lib/3scale/backend/job_fetcher.rb +122 -0
  67. data/lib/3scale/backend/listener.rb +728 -0
  68. data/lib/3scale/backend/listener_metrics.rb +99 -0
  69. data/lib/3scale/backend/logging.rb +48 -0
  70. data/lib/3scale/backend/logging/external.rb +44 -0
  71. data/lib/3scale/backend/logging/external/impl.rb +93 -0
  72. data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
  73. data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
  74. data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
  75. data/lib/3scale/backend/logging/external/resque.rb +57 -0
  76. data/lib/3scale/backend/logging/logger.rb +18 -0
  77. data/lib/3scale/backend/logging/middleware.rb +62 -0
  78. data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
  79. data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
  80. data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
  81. data/lib/3scale/backend/logging/worker.rb +107 -0
  82. data/lib/3scale/backend/manifest.rb +80 -0
  83. data/lib/3scale/backend/memoizer.rb +277 -0
  84. data/lib/3scale/backend/metric.rb +275 -0
  85. data/lib/3scale/backend/metric/collection.rb +91 -0
  86. data/lib/3scale/backend/oauth.rb +4 -0
  87. data/lib/3scale/backend/oauth/token.rb +26 -0
  88. data/lib/3scale/backend/oauth/token_key.rb +30 -0
  89. data/lib/3scale/backend/oauth/token_storage.rb +313 -0
  90. data/lib/3scale/backend/oauth/token_value.rb +25 -0
  91. data/lib/3scale/backend/period.rb +3 -0
  92. data/lib/3scale/backend/period/boundary.rb +107 -0
  93. data/lib/3scale/backend/period/cache.rb +28 -0
  94. data/lib/3scale/backend/period/period.rb +402 -0
  95. data/lib/3scale/backend/queue_storage.rb +16 -0
  96. data/lib/3scale/backend/rack.rb +49 -0
  97. data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
  98. data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
  99. data/lib/3scale/backend/rack/prometheus.rb +19 -0
  100. data/lib/3scale/backend/saas.rb +6 -0
  101. data/lib/3scale/backend/saas_analytics.rb +4 -0
  102. data/lib/3scale/backend/server.rb +30 -0
  103. data/lib/3scale/backend/server/falcon.rb +52 -0
  104. data/lib/3scale/backend/server/puma.rb +71 -0
  105. data/lib/3scale/backend/service.rb +317 -0
  106. data/lib/3scale/backend/service_token.rb +97 -0
  107. data/lib/3scale/backend/stats.rb +8 -0
  108. data/lib/3scale/backend/stats/aggregator.rb +170 -0
  109. data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
  110. data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
  111. data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
  112. data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
  113. data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
  114. data/lib/3scale/backend/stats/cleaner.rb +195 -0
  115. data/lib/3scale/backend/stats/codes_commons.rb +14 -0
  116. data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
  117. data/lib/3scale/backend/stats/key_generator.rb +73 -0
  118. data/lib/3scale/backend/stats/keys.rb +104 -0
  119. data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
  120. data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
  121. data/lib/3scale/backend/stats/period_commons.rb +34 -0
  122. data/lib/3scale/backend/stats/stats_parser.rb +141 -0
  123. data/lib/3scale/backend/stats/storage.rb +113 -0
  124. data/lib/3scale/backend/statsd.rb +14 -0
  125. data/lib/3scale/backend/storable.rb +35 -0
  126. data/lib/3scale/backend/storage.rb +40 -0
  127. data/lib/3scale/backend/storage_async.rb +4 -0
  128. data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
  129. data/lib/3scale/backend/storage_async/client.rb +205 -0
  130. data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
  131. data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
  132. data/lib/3scale/backend/storage_helpers.rb +278 -0
  133. data/lib/3scale/backend/storage_key_helpers.rb +9 -0
  134. data/lib/3scale/backend/storage_sync.rb +43 -0
  135. data/lib/3scale/backend/transaction.rb +62 -0
  136. data/lib/3scale/backend/transactor.rb +177 -0
  137. data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
  138. data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
  139. data/lib/3scale/backend/transactor/notify_job.rb +47 -0
  140. data/lib/3scale/backend/transactor/process_job.rb +33 -0
  141. data/lib/3scale/backend/transactor/report_job.rb +84 -0
  142. data/lib/3scale/backend/transactor/status.rb +236 -0
  143. data/lib/3scale/backend/transactor/usage_report.rb +182 -0
  144. data/lib/3scale/backend/usage.rb +63 -0
  145. data/lib/3scale/backend/usage_limit.rb +115 -0
  146. data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
  147. data/lib/3scale/backend/util.rb +17 -0
  148. data/lib/3scale/backend/validators.rb +26 -0
  149. data/lib/3scale/backend/validators/base.rb +36 -0
  150. data/lib/3scale/backend/validators/key.rb +17 -0
  151. data/lib/3scale/backend/validators/limits.rb +57 -0
  152. data/lib/3scale/backend/validators/oauth_key.rb +15 -0
  153. data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
  154. data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
  155. data/lib/3scale/backend/validators/referrer.rb +60 -0
  156. data/lib/3scale/backend/validators/service_state.rb +15 -0
  157. data/lib/3scale/backend/validators/state.rb +15 -0
  158. data/lib/3scale/backend/version.rb +5 -0
  159. data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
  160. data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
  161. data/lib/3scale/backend/worker.rb +87 -0
  162. data/lib/3scale/backend/worker_async.rb +88 -0
  163. data/lib/3scale/backend/worker_metrics.rb +44 -0
  164. data/lib/3scale/backend/worker_sync.rb +32 -0
  165. data/lib/3scale/bundler_shim.rb +17 -0
  166. data/lib/3scale/prometheus_server.rb +10 -0
  167. data/lib/3scale/tasks/connectivity.rake +41 -0
  168. data/lib/3scale/tasks/helpers.rb +3 -0
  169. data/lib/3scale/tasks/helpers/environment.rb +23 -0
  170. data/lib/3scale/tasks/stats.rake +131 -0
  171. data/lib/3scale/tasks/swagger.rake +46 -0
  172. data/licenses.xml +1215 -0
  173. metadata +227 -0
@@ -0,0 +1,21 @@
1
+ module ThreeScale
2
+ module Backend
3
+ class << self
4
+ def environment
5
+ @environment ||= ENV['RACK_ENV'] || 'development'
6
+ end
7
+
8
+ def production?
9
+ environment == 'production'
10
+ end
11
+
12
+ def development?
13
+ environment == 'development'
14
+ end
15
+
16
+ def test?
17
+ environment == 'test'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,52 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module ErrorStorage
4
+ include StorageHelpers
5
+ extend self
6
+
7
+ PER_PAGE = 100
8
+ MAX_NUM_ERRORS = 1000
9
+
10
+ def store(service_id, error, context_info = {})
11
+ request_info = context_info[:request] || {}
12
+ storage.lpush(queue_key(service_id),
13
+ encode(code: error.code,
14
+ message: error.message,
15
+ timestamp: Time.now.getutc.to_s,
16
+ context_info: request_info))
17
+ storage.ltrim(queue_key(service_id), 0, MAX_NUM_ERRORS - 1)
18
+ end
19
+
20
+ # Pages start at 1, same as in will_paginate.
21
+ def list(service_id, options = {})
22
+ page = options[:page] || 1
23
+ per_page = options[:per_page] || PER_PAGE
24
+ range = pagination_to_range(page.to_i, per_page.to_i)
25
+
26
+ raw_items = (storage.lrange(queue_key(service_id), range.begin, range.end) || [])
27
+ raw_items.map(&method(:decode))
28
+ end
29
+
30
+ def count(service_id)
31
+ storage.llen(queue_key(service_id))
32
+ end
33
+
34
+ def delete_all(service_id)
35
+ storage.del(queue_key(service_id))
36
+ end
37
+
38
+ private
39
+
40
+ def queue_key(service_id)
41
+ "errors/service_id:#{service_id}"
42
+ end
43
+
44
+ def pagination_to_range(page, per_page)
45
+ range_start = (page - 1) * per_page
46
+ range_end = range_start + per_page - 1
47
+
48
+ range_start..range_end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,343 @@
1
+ module ThreeScale
2
+ module Backend
3
+ class Error < RuntimeError
4
+ def to_xml(options = {})
5
+ xml = Builder::XmlMarkup.new
6
+ xml.instruct! unless options[:skip_instruct]
7
+ xml.error(message, :code => code)
8
+
9
+ xml.target!
10
+ end
11
+
12
+ # Note: DON'T change this to http_status; Sinatra will pick it up and break!
13
+ def http_code
14
+ 403
15
+ end
16
+
17
+ def code
18
+ self.class.code
19
+ end
20
+
21
+ def self.code
22
+ underscore(name[/[^:]*$/])
23
+ end
24
+
25
+ def self.underscore(string)
26
+ string.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
27
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
28
+ downcase
29
+ end
30
+ end
31
+
32
+ class BadRequest < Error
33
+ def initialize(msg = 'request contains syntax errors, should not be repeated without modification'.freeze)
34
+ super msg
35
+ end
36
+
37
+ def http_code
38
+ 400
39
+ end
40
+ end
41
+
42
+ class NotFound < Error
43
+ def http_code
44
+ 404
45
+ end
46
+ end
47
+
48
+ class Invalid < Error
49
+ def http_code
50
+ 422
51
+ end
52
+ end
53
+
54
+ class ApplicationKeyInvalid < Error
55
+ def initialize(key)
56
+ if key.blank?
57
+ super 'application key is missing'.freeze
58
+ else
59
+ super %(application key "#{key}" is invalid)
60
+ end
61
+ end
62
+ end
63
+
64
+ class ApplicationHasInconsistentData < Error
65
+ def initialize(id, user_key)
66
+ super %(Application id="#{id}" with user_key="#{user_key}" has inconsistent data and could not be saved)
67
+ end
68
+ end
69
+
70
+ class ApplicationNotFound < NotFound
71
+ def initialize(id = nil)
72
+ super %(application with id="#{id}" was not found)
73
+ end
74
+ end
75
+
76
+ class AccessTokenInvalid < NotFound
77
+ def initialize(id = nil)
78
+ super %(token "#{id}" is invalid: expired or never defined)
79
+ end
80
+ end
81
+
82
+ class AccessTokenAlreadyExists < Error
83
+ def initialize(id = nil)
84
+ super %(token "#{id}" already exists)
85
+ end
86
+ end
87
+
88
+ class AccessTokenStorageError < Error
89
+ def initialize(id = nil)
90
+ super %(storage error when saving token "#{id}")
91
+ end
92
+ end
93
+
94
+ class AccessTokenFormatInvalid < Invalid
95
+ def initialize
96
+ super 'token is either too big or has an invalid format'.freeze
97
+ end
98
+ end
99
+
100
+ class AccessTokenInvalidTTL < Invalid
101
+ def initialize
102
+ super 'the specified TTL should be a positive integer'.freeze
103
+ end
104
+ end
105
+
106
+ class ServiceNotActive < Error
107
+ def initialize
108
+ super 'service is not active'.freeze
109
+ end
110
+ end
111
+
112
+ class ApplicationNotActive < Error
113
+ def initialize
114
+ super 'application is not active'.freeze
115
+ end
116
+ end
117
+
118
+ class OauthNotEnabled < Error
119
+ def initialize
120
+ super 'oauth is not enabled'.freeze
121
+ end
122
+ end
123
+
124
+ class RedirectURIInvalid < Error
125
+ def initialize(uri)
126
+ super %(redirect_uri "#{uri}" is invalid)
127
+ end
128
+ end
129
+
130
+ class RedirectURLInvalid < Error
131
+ def initialize(url)
132
+ super %(redirect_url "#{url}" is invalid)
133
+ end
134
+ end
135
+
136
+ class LimitsExceeded < Error
137
+ def initialize
138
+ super 'usage limits are exceeded'.freeze
139
+ end
140
+ end
141
+
142
+ class ProviderKeyInvalid < Error
143
+ def initialize(key)
144
+ super %(provider key "#{key}" is invalid)
145
+ end
146
+ end
147
+
148
+ class ServiceIdInvalid < Error
149
+ def initialize(id)
150
+ super %(service id "#{id}" is invalid)
151
+ end
152
+ end
153
+
154
+ class MetricInvalid < NotFound
155
+ def initialize(metric_name)
156
+ super %(metric "#{metric_name}" is invalid)
157
+ end
158
+ end
159
+
160
+ class ReferrerFilterInvalid < Invalid
161
+ end
162
+
163
+ class NotValidData < Invalid
164
+ def initialize
165
+ super 'all data must be valid UTF8'.freeze
166
+ end
167
+ end
168
+
169
+ class ReferrerFiltersMissing < Error
170
+ def initialize
171
+ super 'referrer filters are missing'.freeze
172
+ end
173
+ end
174
+
175
+ class ReferrerNotAllowed < Error
176
+ def initialize(referrer)
177
+ if referrer.blank?
178
+ super 'referrer is missing'.freeze
179
+ else
180
+ super %(referrer "#{referrer}" is not allowed)
181
+ end
182
+ end
183
+ end
184
+
185
+ class RequiredParamsMissing < Invalid
186
+ def initialize
187
+ super 'missing required parameters'.freeze
188
+ end
189
+ end
190
+
191
+ class UsageValueInvalid < Error
192
+ def initialize(metric_name, value)
193
+ if !value.is_a?(String) || value.blank?
194
+ super %(usage value for metric "#{metric_name}" can not be empty)
195
+ else
196
+ super %(usage value "#{value}" for metric "#{metric_name}" is invalid)
197
+ end
198
+ end
199
+ end
200
+
201
+ class UnsupportedApiVersion < Error
202
+ end
203
+
204
+ class TransactionTimestampNotWithinRange < Error
205
+ end
206
+
207
+ class TransactionTimestampTooOld < TransactionTimestampNotWithinRange
208
+ def initialize(max_seconds)
209
+ super %(reporting transactions older than #{max_seconds} seconds is not allowed)
210
+ end
211
+ end
212
+
213
+ class TransactionTimestampTooNew < TransactionTimestampNotWithinRange
214
+ def initialize(max_seconds)
215
+ super %(reporting transactions more than #{max_seconds} seconds in the future is not allowed)
216
+ end
217
+ end
218
+
219
+ class ServiceLoadInconsistency < Error
220
+ def initialize(service_id, other_service_id)
221
+ super %(service.load_by_id with id="#{service_id}" loaded the service with id="#{other_service_id}")
222
+ end
223
+ end
224
+
225
+ class ServiceIsDefaultService < Error
226
+ def initialize(id = nil)
227
+ super %(Service id="#{id}" is the default service, cannot be removed)
228
+ end
229
+ end
230
+
231
+ class InvalidProviderKeys < Error
232
+ def initialize
233
+ super 'Provider keys are not valid, must be not nil and different'.freeze
234
+ end
235
+ end
236
+
237
+ class ProviderKeyExists < Error
238
+ def initialize(key)
239
+ super %(Provider key="#{key}" already exists)
240
+ end
241
+ end
242
+
243
+ class ProviderKeyNotFound < Error
244
+ def initialize(key)
245
+ super %(Provider key="#{key}" does not exist)
246
+ end
247
+ end
248
+
249
+ class InvalidEventType < Error
250
+ def initialize(type)
251
+ super %(Event type "#{type}" is invalid")
252
+ end
253
+ end
254
+
255
+ class ProviderKeyOrServiceTokenRequired < Error
256
+ def initialize
257
+ super 'Provider key or service token are required'.freeze
258
+ end
259
+ end
260
+
261
+ class ServiceIdMissing < Invalid
262
+ def initialize
263
+ super 'Service ID is missing'.freeze
264
+ end
265
+ end
266
+
267
+ # The name for this class stays as ServiceTokenInvalid even though the more
268
+ # correct name would be ServiceTokenOrIdInvalid to avoid breaking users.
269
+ class ServiceTokenInvalid < Error
270
+ def initialize(token, service_id)
271
+ super %(service token "#{token}" or service id "#{service_id}" is invalid)
272
+ end
273
+ end
274
+
275
+ # This is raised in these 2 situations:
276
+ # 1) The request does not contain a valid provider key nor a service ID.
277
+ # 2) The request contains a valid provider key, but does not contain a
278
+ # service ID, and the provider does not have a default service
279
+ # associated.
280
+ class ProviderKeyInvalidOrServiceMissing < Error
281
+ def initialize(provider_key)
282
+ super %(provider key "#{provider_key}" invalid and/or service ID missing)
283
+ end
284
+ end
285
+
286
+ # Legacy API support
287
+
288
+ class AuthenticationError < Error
289
+ def initialize
290
+ super 'either app_id or user_key is allowed, not both'.freeze
291
+ end
292
+ end
293
+
294
+ class UserKeyInvalid < Error
295
+ def initialize(key)
296
+ super %(user key "#{key}" is invalid)
297
+ end
298
+ end
299
+
300
+ # Bad Requests
301
+ class ContentTypeInvalid < BadRequest
302
+ def initialize(content_type)
303
+ super %(invalid Content-Type: #{content_type})
304
+ end
305
+ end
306
+
307
+ class TransactionsIsBlank < BadRequest
308
+ def initialize
309
+ super 'transactions parameter is blank'.freeze
310
+ end
311
+ end
312
+
313
+ class TransactionsFormatInvalid < BadRequest
314
+ def initialize
315
+ super 'transactions format is invalid'.freeze
316
+ end
317
+ end
318
+
319
+ class TransactionsHasNilTransaction < BadRequest
320
+ def initialize
321
+ super 'transactions has a nil transaction'.freeze
322
+ end
323
+ end
324
+
325
+ class ApplicationHasNoState < BadRequest
326
+ def initialize(id)
327
+ super %(Application with id="#{id}" has no state )
328
+ end
329
+ end
330
+
331
+ class DeleteServiceStatsValidationError < Error
332
+ def initialize(service_id, msg)
333
+ super "Delete stats job context validation error. Service: #{service_id}. Error: #{msg}"
334
+ end
335
+ end
336
+
337
+ class EndUsersNoLongerSupported < BadRequest
338
+ def initialize
339
+ super 'End-users are no longer supported, do not specify the user_id parameter'.freeze
340
+ end
341
+ end
342
+ end
343
+ end
@@ -0,0 +1,120 @@
1
+ require 'net/http'
2
+
3
+ module ThreeScale
4
+ module Backend
5
+ class EventStorage
6
+ PING_TTL = 60
7
+ EVENT_TYPES = [:first_traffic, :first_daily_traffic, :alert]
8
+
9
+ class << self
10
+ include StorageHelpers
11
+
12
+ def store(type, object)
13
+ fail InvalidEventType, type unless EVENT_TYPES.member?(type)
14
+ new_id = storage.incrby(events_id_key, 1)
15
+ event = { id: new_id, type: type, timestamp: Time.now.utc, object: object }
16
+ storage.zadd(events_queue_key, event[:id], encode(event))
17
+ end
18
+
19
+ def list
20
+ raw_events = storage.zrevrange(events_queue_key, 0, -1)
21
+ raw_events.map { |raw_event| decode_event(raw_event) }.reverse
22
+ end
23
+
24
+ def delete_range(to_id)
25
+ to_id = to_id.to_i
26
+ if to_id > 0
27
+ storage.zremrangebyscore(events_queue_key, 0, to_id)
28
+ else
29
+ 0
30
+ end
31
+ end
32
+
33
+ def delete(id)
34
+ id = id.to_i
35
+ (id > 0) ? storage.zremrangebyscore(events_queue_key, id, id) : 0
36
+ end
37
+
38
+ def size
39
+ storage.zcard(events_queue_key)
40
+ end
41
+
42
+ def ping_if_not_empty
43
+ if events_hook && pending_ping?
44
+ request_to_events_hook
45
+ true
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def events_queue_key
52
+ "events/queue".freeze
53
+ end
54
+
55
+ def events_ping_key
56
+ "events/ping".freeze
57
+ end
58
+
59
+ def events_id_key
60
+ "events/id".freeze
61
+ end
62
+
63
+ def request_to_events_hook
64
+ Net::HTTP.post_form(
65
+ events_hook_uri,
66
+ secret: events_hook_shared_secret,
67
+ )
68
+ end
69
+
70
+ def events_hook
71
+ hook = Backend.configuration.events_hook
72
+ if hook.nil? || hook.empty?
73
+ false
74
+ else
75
+ hook
76
+ end
77
+ end
78
+
79
+ def events_hook_shared_secret
80
+ Backend.configuration.events_hook_shared_secret
81
+ end
82
+
83
+ def events_hook_uri
84
+ URI(events_hook)
85
+ end
86
+
87
+ def expire_last_ping
88
+ storage.expire(events_ping_key, PING_TTL)
89
+ end
90
+
91
+ def pending_ping?
92
+ ## the queue is not empty and more than timeout has passed
93
+ ## since the front-end was notified
94
+ events_set_size, ping_key_value = storage.pipelined do
95
+ storage.zcard(events_queue_key)
96
+ storage.incr(events_ping_key)
97
+ end
98
+
99
+ return false unless ping_key_value.to_i == 1
100
+ expire_last_ping
101
+ events_set_size > 0
102
+ end
103
+
104
+ def decode_event(raw_event)
105
+ event = decode(raw_event)
106
+
107
+ # decode only symbolizes keys and parse timestamp for first level
108
+ obj = event[:object]
109
+ if obj
110
+ event[:object] = obj.symbolize_names
111
+ ts = event[:object][:timestamp]
112
+ event[:object][:timestamp] = Time.parse_to_utc(ts) if ts
113
+ end
114
+
115
+ event
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end