omni_events 0.1.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.
data/README.md ADDED
@@ -0,0 +1,461 @@
1
+ # 🚀 OmniEvent
2
+
3
+ > *"One Gem to rule them all, One Gem to find them, One Gem to bring all logs, and in the shadows, trace them."*
4
+
5
+ [![Gem Version](https://img.shields.io/badge/gem-v0.1.1-blue.svg)](#)
6
+ [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](#)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![Rails Version](https://img.shields.io/badge/rails-6.1+-red.svg)](#)
9
+
10
+ **OmniEvent** is a production-ready Rails Engine that unifies your system's entire event lifecycle — from secure external webhook ingestion to detailed internal process auditing — through a single, traceable pipeline.
11
+
12
+ ---
13
+
14
+ ## Table of Contents
15
+
16
+ - [Key Features](#-key-features)
17
+ - [Installation](#️-installation)
18
+ - [Configuration](#-configuration)
19
+ - [Receiving Webhooks](#-receiving-webhooks)
20
+ - [Processing Pipeline](#-processing-pipeline)
21
+ - [Polymorphic Logging](#-polymorphic-logging)
22
+ - [Security](#-security)
23
+ - [Database Maintenance](#️-database-maintenance)
24
+ - [Testing](#-testing)
25
+ - [Docker Development](#-docker-development)
26
+
27
+ ---
28
+
29
+ ## 🌟 Key Features
30
+
31
+ - **Secure Webhook Receiver** — token auth, IP whitelisting, HMAC signature verification, and replay attack protection out of the box.
32
+ - **Step Pipeline** — organize complex business logic into traceable steps with automatic error capturing and context logging.
33
+ - **Polymorphic Logging** — attach structured logs to any model (`Order`, `User`, `Payment`, etc.) with a unified API.
34
+ - **Processor Registry** — map each webhook source (Notifier) to its own processor class via configuration.
35
+ - **Async Monitoring** — non-blocking New Relic Insights integration via ActiveJob.
36
+ - **Smart Cleanup** — built-in Rake task for data retention based on configurable `retention_days`.
37
+ - **Zero-Boilerplate DX** — Devise-like installation, Rails callback-inspired syntax.
38
+
39
+ ---
40
+
41
+ ## 🛠️ Installation
42
+
43
+ **1. Add to your Gemfile:**
44
+
45
+ ```ruby
46
+ gem 'omni_event'
47
+ ```
48
+
49
+ **2. Run the installer:**
50
+
51
+ ```bash
52
+ bundle install
53
+ rails generate omni_event:install
54
+ rails db:migrate
55
+ ```
56
+
57
+ The generator creates:
58
+ - `config/initializers/omni_event.rb` — your configuration file
59
+ - `app/models/log.rb` — local proxy for `OmniEvent::Log`
60
+ - `app/models/webhook_event.rb` — local proxy for `OmniEvent::WebhookEvent`
61
+
62
+ **3. Mount the engine in `config/routes.rb`:**
63
+
64
+ ```ruby
65
+ mount OmniEvent::Engine => "/omni_events"
66
+ # Exposes: POST /omni_events/receiver/:token
67
+ ```
68
+
69
+ ---
70
+
71
+ ## ⚙️ Configuration
72
+
73
+ All options live in `config/initializers/omni_event.rb`:
74
+
75
+ ```ruby
76
+ OmniEvent.configure do |config|
77
+ # ── Monitoring ─────────────────────────────────────────────────────────────
78
+ config.new_relic_enabled = true
79
+ config.new_relic_api_key = ENV['NEW_RELIC_KEY']
80
+ config.new_relic_account_id = ENV['NEW_RELIC_ACCOUNT_ID']
81
+
82
+ # ── Processing ─────────────────────────────────────────────────────────────
83
+ config.process_async = true # false = synchronous (useful for testing)
84
+ config.retention_days = 30 # used by rake omni_event:cleanup
85
+
86
+ # ── Custom log types ────────────────────────────────────────────────────────
87
+ # Define domain-specific action types for your business context.
88
+ config.custom_log_types = {
89
+ system_info: 0,
90
+ system_error: 1,
91
+ payment_received: 10,
92
+ user_update: 20,
93
+ fiscal_validation: 30
94
+ }
95
+
96
+ # ── Processor registry ──────────────────────────────────────────────────────
97
+ # Maps each Notifier name to the processor class that handles its events.
98
+ config.processors = {
99
+ "PaymentGateway" => Webhooks::PaymentGatewayProcessor,
100
+ "BillingService" => Webhooks::BillingServiceProcessor,
101
+ "CrmSystem" => Webhooks::CrmSystemProcessor
102
+ }
103
+ end
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 📡 Receiving Webhooks
109
+
110
+ ### 1. Create a Notifier
111
+
112
+ A **Notifier** represents one external webhook source (e.g. a payment gateway, a payment processor). Each has its own security configuration.
113
+
114
+ ```ruby
115
+ # Minimal — token auth only
116
+ notifier = OmniEvent::Notifier.create!(name: "Stripe")
117
+ # => token is auto-generated: SecureRandom.hex(24)
118
+
119
+ # Full security configuration
120
+ notifier = OmniEvent::Notifier.create!(
121
+ name: "Payment Gateway",
122
+ secret_key: ENV['WEBHOOK_SECRET'], # enables HMAC verification
123
+ timestamp_tolerance: 300, # 5-minute replay window (seconds)
124
+ check_ip: true,
125
+ allowed_ips: ["185.60.216.35", "185.60.218.35"]
126
+ )
127
+
128
+ # The webhook endpoint for this notifier:
129
+ # POST /omni_events/receiver/#{notifier.token}
130
+ puts notifier.token # => "a3f9c2b1e4d7..."
131
+ ```
132
+
133
+ ### 2. Register the processor
134
+
135
+ In `config/initializers/omni_event.rb`, map the notifier name to a processor class:
136
+
137
+ ```ruby
138
+ config.processors = {
139
+ "Payment Gateway" => Webhooks::PaymentGatewayProcessor
140
+ }
141
+ ```
142
+
143
+ ### 3. Send a webhook
144
+
145
+ The partner sends a `POST` request to your endpoint:
146
+
147
+ ```bash
148
+ curl -X POST https://yourapp.com/omni_events/receiver/a3f9c2b1e4d7... \
149
+ -H "Content-Type: application/json" \
150
+ -H "X-OmniEvent-Timestamp: $(date +%s)" \
151
+ -H "X-OmniEvent-Signature: sha256=$(echo -n '{"event":"payment.confirmed"}' | openssl dgst -sha256 -hmac 'your_secret')" \
152
+ -d '{"event":"payment.confirmed","charge_id":"ch_abc123","status":"paid"}'
153
+ ```
154
+
155
+ The receiver will:
156
+ 1. Validate payload size (max 1MB)
157
+ 2. Authenticate via token
158
+ 3. Check IP whitelist (if enabled)
159
+ 4. Verify HMAC signature + timestamp (if `secret_key` is set)
160
+ 5. Persist the `WebhookEvent`
161
+ 6. Dispatch to the registered processor (async or sync)
162
+
163
+ ---
164
+
165
+ ## 🔄 Processing Pipeline
166
+
167
+ Define your business logic as a sequence of named steps. OmniEvent automatically logs any step failure with full context (step name, error class, backtrace).
168
+
169
+ ```ruby
170
+ # app/services/webhooks/payment_gateway_processor.rb
171
+ class Webhooks::PaymentGatewayProcessor < OmniEvent::BaseProcessor
172
+ steps :validate_payload,
173
+ :update_payment_status,
174
+ :notify_customer,
175
+ :record_audit_log
176
+
177
+ def validate_payload
178
+ raise "Missing charge ID" if event.payload[:charge_id].blank?
179
+ raise "Unknown status '#{event.payload[:status]}'" unless valid_status?
180
+ end
181
+
182
+ def update_payment_status
183
+ payment.update!(status: event.payload[:status])
184
+ end
185
+
186
+ def notify_customer
187
+ CustomerMailer.payment_update(payment).deliver_later
188
+ end
189
+
190
+ def record_audit_log
191
+ Log.create!(
192
+ loggable: payment,
193
+ action_type: :payment_processed,
194
+ content: "Payment status updated to '#{event.payload[:status]}'",
195
+ metadata: { gateway: "Stripe", source: "webhook", timestamp: Time.current.iso8601 }
196
+ )
197
+ end
198
+
199
+ private
200
+
201
+ def payment
202
+ @payment ||= Payment.find_by!(charge_id: event.payload[:charge_id])
203
+ end
204
+
205
+ def valid_status?
206
+ %w[paid pending failed refunded disputed].include?(event.payload[:status])
207
+ end
208
+ end
209
+ ```
210
+
211
+ When a step raises an error, OmniEvent automatically creates a `system_error` log with the context and re-raises so the job can retry:
212
+
213
+ ```ruby
214
+ # Auto-created by OmniEvent on step failure:
215
+ OmniEvent::Log.create!(
216
+ loggable: event,
217
+ action_type: :system_error,
218
+ content: "FAILURE in step [Validate payload]: Missing charge ID",
219
+ metadata: {
220
+ error_class: "RuntimeError",
221
+ method: :validate_payload,
222
+ backtrace: [...]
223
+ }
224
+ )
225
+ ```
226
+
227
+ ---
228
+
229
+ ## 📋 Polymorphic Logging
230
+
231
+ Use `Log` (the local proxy generated by the installer) to attach structured log entries to any model.
232
+
233
+ ```ruby
234
+ # Attach to any ActiveRecord model
235
+ Log.create!(
236
+ loggable: @order,
237
+ action_type: :payment_received,
238
+ content: "Payment of R$ 1.250,00 confirmed via PIX",
239
+ metadata: { gateway: "Stripe", charge_id: "ch_abc123", amount_cents: 125_000 }
240
+ )
241
+
242
+ # Query logs for a specific record
243
+ @order.logs.where(action_type: :system_error).order(created_at: :desc)
244
+
245
+ # Custom scopes on your local Log model (app/models/log.rb)
246
+ class Log < OmniEvent::Log
247
+ scope :recent_errors, -> { where(action_type: :system_error).where('created_at > ?', 24.hours.ago) }
248
+ scope :for_gateway, ->(gw) { where("metadata->>'gateway' = ?", gw) }
249
+ end
250
+ ```
251
+
252
+ ### Custom log types
253
+
254
+ Define your domain vocabulary in the initializer:
255
+
256
+ ```ruby
257
+ config.custom_log_types = {
258
+ system_info: 0,
259
+ system_error: 1,
260
+ payment_received: 10,
261
+ payment_failed: 11,
262
+ payment_processed: 20,
263
+ fiscal_validation: 30
264
+ }
265
+ ```
266
+
267
+ ---
268
+
269
+ ## 🔒 Security
270
+
271
+ OmniEvent provides **4 independent security layers**, all configurable per Notifier. Each layer is opt-in and backward compatible.
272
+
273
+ ### Layer 1 — Token Authentication
274
+
275
+ Every webhook endpoint is identified by a unique, cryptographically random token (48-char hex). Requests without a valid token receive `401 Unauthorized`.
276
+
277
+ ```ruby
278
+ notifier = OmniEvent::Notifier.create!(name: "Partner")
279
+ # Endpoint: POST /omni_events/receiver/#{notifier.token}
280
+ ```
281
+
282
+ ### Layer 2 — IP Whitelisting
283
+
284
+ Restrict which IPs can send requests to each notifier.
285
+
286
+ ```ruby
287
+ OmniEvent::Notifier.create!(
288
+ name: "Stripe",
289
+ check_ip: true,
290
+ allowed_ips: ["54.187.174.169", "54.187.205.235"]
291
+ )
292
+ ```
293
+
294
+ Requests from non-whitelisted IPs receive `403 Forbidden`.
295
+
296
+ ### Layer 3 — HMAC Signature Verification
297
+
298
+ The gold standard for webhook security. The sender signs the raw request body with a shared secret using HMAC-SHA256. OmniEvent verifies the signature using constant-time comparison (preventing timing attacks).
299
+
300
+ ```ruby
301
+ OmniEvent::Notifier.create!(
302
+ name: "Stripe",
303
+ secret_key: ENV['STRIPE_WEBHOOK_SECRET'] # e.g. "whsec_abc123..."
304
+ )
305
+ ```
306
+
307
+ **Required header from the sender:**
308
+ ```
309
+ X-OmniEvent-Signature: sha256=<HMAC-SHA256(secret_key, raw_body)>
310
+ ```
311
+
312
+ **Example — generating the signature (sender side):**
313
+
314
+ ```ruby
315
+ # Ruby
316
+ signature = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', secret_key, raw_body)}"
317
+
318
+ # Node.js
319
+ const sig = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
320
+
321
+ # Python
322
+ import hmac, hashlib
323
+ sig = 'sha256=' + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
324
+ ```
325
+
326
+ ### Layer 4 — Replay Attack Protection
327
+
328
+ When `secret_key` is set, OmniEvent also validates a timestamp header to reject requests that are too old — preventing replay attacks where a valid captured request is re-sent.
329
+
330
+ ```ruby
331
+ OmniEvent::Notifier.create!(
332
+ name: "Stripe",
333
+ secret_key: ENV['STRIPE_WEBHOOK_SECRET'],
334
+ timestamp_tolerance: 300 # reject requests older than 5 minutes (default)
335
+ )
336
+ ```
337
+
338
+ **Required header from the sender:**
339
+ ```
340
+ X-OmniEvent-Timestamp: <Unix timestamp, e.g. 1711800000>
341
+ ```
342
+
343
+ Set `timestamp_tolerance: 0` to disable timestamp checking while keeping signature verification.
344
+
345
+ ### Layer 5 — Payload Size Limit
346
+
347
+ All requests are automatically capped at **1MB**. Oversized payloads receive `413 Payload Too Large` before any processing occurs.
348
+
349
+ ### Complete security setup example
350
+
351
+ ```ruby
352
+ # Notifier with all layers active
353
+ notifier = OmniEvent::Notifier.create!(
354
+ name: "Stripe Payments",
355
+ secret_key: ENV['STRIPE_WEBHOOK_SECRET'],
356
+ timestamp_tolerance: 300,
357
+ check_ip: true,
358
+ allowed_ips: ["185.60.216.35"]
359
+ )
360
+
361
+ # Check which security features are active
362
+ notifier.signature_verification? # => true
363
+ notifier.check_ip? # => true
364
+ ```
365
+
366
+ ### Security response codes
367
+
368
+ | Condition | HTTP Status |
369
+ |---|---|
370
+ | Payload > 1MB | `413 Payload Too Large` |
371
+ | Invalid or missing token | `401 Unauthorized` |
372
+ | IP not whitelisted | `403 Forbidden` |
373
+ | Invalid/missing signature | `401 Unauthorized` |
374
+ | Timestamp outside window | `401 Unauthorized` |
375
+
376
+ ---
377
+
378
+ ## 🗄️ Database Maintenance
379
+
380
+ Prevent database bloating by periodically deleting old records:
381
+
382
+ ```bash
383
+ rake omni_event:cleanup
384
+ # => [OmniEvent] Cleanup complete: 1543 logs and 892 webhook events deleted (older than 30 days).
385
+ ```
386
+
387
+ Configure the retention period in your initializer:
388
+
389
+ ```ruby
390
+ config.retention_days = 90 # keep records for 90 days
391
+ ```
392
+
393
+ Schedule it in production (e.g. with `whenever` or Heroku Scheduler):
394
+
395
+ ```ruby
396
+ # config/schedule.rb (whenever gem)
397
+ every 1.day, at: '2:00 am' do
398
+ rake "omni_event:cleanup"
399
+ end
400
+ ```
401
+
402
+ ---
403
+
404
+ ## 🧪 Testing
405
+
406
+ ### Unit tests (no database required)
407
+
408
+ ```bash
409
+ bundle exec rspec
410
+ ```
411
+
412
+ ### Integration tests (requires the dummy Rails app)
413
+
414
+ ```bash
415
+ INTEGRATION=1 bundle exec rspec
416
+ ```
417
+
418
+ ### Testing your processors
419
+
420
+ ```ruby
421
+ RSpec.describe Webhooks::PaymentGatewayProcessor do
422
+ let(:notifier) { create(:omni_event_notifier) }
423
+ let(:event) { create(:omni_event_webhook_event, webhook_notifier: notifier, payload: { charge_id: "ch_abc123", status: "paid" }) }
424
+
425
+ it "updates the payment status" do
426
+ payment = create(:payment, charge_id: "ch_abc123")
427
+ described_class.new(event).process!
428
+ expect(payment.reload.status).to eq("paid")
429
+ end
430
+
431
+ it "creates an audit log" do
432
+ create(:payment, charge_id: "ch_abc123")
433
+ expect { described_class.new(event).process! }.to change(Log, :count).by(1)
434
+ end
435
+
436
+ it "creates a system_error log when a step fails" do
437
+ allow_any_instance_of(described_class).to receive(:validate_payload).and_raise("boom")
438
+ expect { described_class.new(event).process! }.to raise_error("boom")
439
+ expect(OmniEvent::Log.last.action_type).to eq("system_error")
440
+ end
441
+ end
442
+ ```
443
+
444
+ ---
445
+
446
+ ## 🐳 Docker Development
447
+
448
+ ```bash
449
+ docker compose up -d
450
+ docker compose exec app bash
451
+ bundle exec rspec
452
+ rake omni_event:cleanup
453
+ ```
454
+
455
+ ---
456
+
457
+ ## 📄 License
458
+
459
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
460
+
461
+ Developed with ❤️ by [Antonio Neto](https://github.com/antonioneto1)
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,38 @@
1
+ module OmniEvent
2
+ class ReceiverController < ActionController::API
3
+ MAX_PAYLOAD_SIZE = 1.megabyte
4
+
5
+ # POST /omni_events/receiver/:token
6
+ def create
7
+ # ── 1. Payload size guard ──────────────────────────────────────────────
8
+ if request.content_length.to_i > MAX_PAYLOAD_SIZE
9
+ return render json: { error: 'Payload too large' }, status: :payload_too_large
10
+ end
11
+
12
+ # ── 2. Token authentication ────────────────────────────────────────────
13
+ notifier = OmniEvent::Notifier.find_by(token: params[:token])
14
+ return render json: { error: 'Unauthorized' }, status: :unauthorized unless notifier
15
+
16
+ # ── 3. IP whitelist ────────────────────────────────────────────────────
17
+ if notifier.check_ip? && !notifier.allows_ip?(request.remote_ip)
18
+ return render json: { error: 'Forbidden — IP not whitelisted' }, status: :forbidden
19
+ end
20
+
21
+ # ── 4. HMAC signature + replay attack protection ───────────────────────
22
+ verification = OmniEvent::SignatureVerifier.call(notifier, request)
23
+ unless verification.success?
24
+ Rails.logger.warn "[OmniEvent] Security check failed for notifier '#{notifier.name}': #{verification.error}"
25
+ return render json: { error: verification.error }, status: :unauthorized
26
+ end
27
+
28
+ # ── 5. Store and dispatch ──────────────────────────────────────────────
29
+ event = OmniEvent::WebhookEvent.create_from_request!(notifier, request)
30
+ event.dispatch!
31
+
32
+ render json: { received: true, event_id: event.id }, status: :ok
33
+ rescue => e
34
+ Rails.logger.error "[OmniEvent] ReceiverController error: #{e.message}"
35
+ render json: { error: 'Internal error' }, status: :unprocessable_entity
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ module OmniEvent
2
+ class NewRelicJob < ActiveJob::Base
3
+ queue_as :default
4
+
5
+ def perform(log_attributes)
6
+ config = OmniEvent.configuration
7
+
8
+ return unless config.new_relic_enabled
9
+ return unless config.new_relic_api_key.present?
10
+ return unless config.new_relic_account_id.present?
11
+
12
+ payload = build_payload(log_attributes)
13
+
14
+ response = HTTParty.post(
15
+ "https://insights-collector.newrelic.com/v1/accounts/#{config.new_relic_account_id}/events",
16
+ headers: {
17
+ "Content-Type" => "application/json",
18
+ "X-Insert-Key" => config.new_relic_api_key
19
+ },
20
+ body: payload.to_json
21
+ )
22
+
23
+ unless response.success?
24
+ Rails.logger.warn "[OmniEvent] NewRelicJob: unexpected response #{response.code}"
25
+ end
26
+ rescue => e
27
+ Rails.logger.error "[OmniEvent] NewRelicJob failed: #{e.message}"
28
+ end
29
+
30
+ private
31
+
32
+ def build_payload(attrs)
33
+ metadata = attrs["metadata"].is_a?(String) ? JSON.parse(attrs["metadata"]) : attrs["metadata"].to_h
34
+
35
+ {
36
+ eventType: "OmniEventLog",
37
+ actionType: attrs["action_type"],
38
+ content: attrs["content"],
39
+ loggableType: attrs["loggable_type"],
40
+ loggableId: attrs["loggable_id"],
41
+ timestamp: attrs["created_at"].to_i
42
+ }.merge(metadata)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,10 @@
1
+ module OmniEvent
2
+ class ProcessWebhookJob < ActiveJob::Base
3
+ queue_as :default
4
+
5
+ def perform(event_id)
6
+ event = OmniEvent::WebhookEvent.find(event_id)
7
+ OmniEvent::ProcessDispatcher.call(event)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module OmniEvent
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ module OmniEvent
2
+ class Log < ApplicationRecord
3
+ self.table_name = "omni_event_logs"
4
+
5
+ belongs_to :loggable, polymorphic: true, optional: true
6
+ has_one_attached :payload_debug
7
+
8
+ serialize :metadata, coder: JSON
9
+
10
+ after_create_commit :dispatch_external_monitoring
11
+
12
+ private
13
+
14
+ def dispatch_external_monitoring
15
+ OmniEvent::NewRelicJob.perform_later(attributes)
16
+ rescue => e
17
+ Rails.logger.error "[OmniEvent] Failed to enqueue NewRelicJob: #{e.message}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ module OmniEvent
2
+ class Notifier < ApplicationRecord
3
+ self.table_name = "omni_event_notifiers"
4
+
5
+ has_many :webhook_events,
6
+ class_name: "OmniEvent::WebhookEvent",
7
+ foreign_key: :webhook_notifier_id,
8
+ dependent: :destroy
9
+
10
+ validates :name, presence: true
11
+ validates :token, presence: true, uniqueness: true
12
+
13
+ before_validation :generate_token, on: :create
14
+
15
+ # Returns true if the given IP is allowed to send requests.
16
+ # Skipped when check_ip is false.
17
+ def allows_ip?(ip)
18
+ return true unless check_ip?
19
+
20
+ Array(allowed_ips).include?(ip.to_s)
21
+ end
22
+
23
+ # Returns true if HMAC signature verification is active for this notifier.
24
+ def signature_verification?
25
+ secret_key.present?
26
+ end
27
+
28
+ private
29
+
30
+ def generate_token
31
+ self.token ||= SecureRandom.hex(24)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ module OmniEvent
2
+ class WebhookEvent < ApplicationRecord
3
+ self.table_name = "omni_event_webhook_events"
4
+
5
+ belongs_to :webhook_notifier, class_name: "OmniEvent::Notifier"
6
+
7
+ serialize :headers, coder: JSON
8
+ serialize :payload, coder: JSON
9
+
10
+ enum status: {
11
+ pending: 'pending',
12
+ processing: 'processing',
13
+ processed: 'processed',
14
+ failed: 'failed'
15
+ }
16
+
17
+ def self.create_from_request!(notifier, request)
18
+ create!(
19
+ webhook_notifier: notifier,
20
+ headers: request.headers.to_h.select { |k, _| k == k.upcase },
21
+ payload: request.request_parameters.presence ||
22
+ request.query_parameters.presence ||
23
+ request.raw_post
24
+ )
25
+ end
26
+
27
+ def dispatch!
28
+ if OmniEvent.configuration.process_async
29
+ OmniEvent::ProcessWebhookJob.perform_later(id)
30
+ else
31
+ OmniEvent::ProcessDispatcher.call(self)
32
+ end
33
+ end
34
+ end
35
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ OmniEvent::Engine.routes.draw do
2
+ post 'receiver/:token', to: 'receiver#create', as: :receiver
3
+ end
@@ -0,0 +1,13 @@
1
+ class CreateOmniEventNotifiers < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :omni_event_notifiers do |t|
4
+ t.string :name, null: false
5
+ t.string :token, null: false
6
+ t.boolean :check_ip, null: false, default: false
7
+ t.jsonb :allowed_ips, null: false, default: []
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :omni_event_notifiers, :token, unique: true
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ class CreateOmniEventWebhookEvents < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :omni_event_webhook_events do |t|
4
+ t.references :webhook_notifier, null: false,
5
+ foreign_key: { to_table: :omni_event_notifiers }
6
+ t.jsonb :headers, null: false, default: {}
7
+ t.jsonb :payload, null: false, default: {}
8
+ t.string :status, null: false, default: 'pending'
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :omni_event_webhook_events, :status
13
+ end
14
+ end