fuik 0.12.0 → 0.13.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +156 -25
  3. data/app/assets/stylesheets/fuik/application.css +152 -88
  4. data/app/controllers/concerns/fuik/event_type.rb +32 -7
  5. data/app/controllers/fuik/events_controller.rb +6 -1
  6. data/app/controllers/fuik/retries_controller.rb +12 -0
  7. data/app/controllers/fuik/webhooks_controller.rb +19 -15
  8. data/app/jobs/fuik/webhook_processing_job.rb +14 -0
  9. data/app/models/fuik/event.rb +36 -0
  10. data/app/models/fuik/webhook_event/filterable.rb +3 -4
  11. data/app/models/fuik/webhook_event.rb +32 -1
  12. data/app/views/fuik/events/index.html.erb +5 -1
  13. data/app/views/fuik/events/show.html.erb +43 -20
  14. data/app/views/layouts/fuik/application.html.erb +11 -1
  15. data/config/routes.rb +4 -1
  16. data/lib/fuik/configuration.rb +29 -0
  17. data/lib/fuik/engine.rb +0 -4
  18. data/lib/fuik/errors.rb +5 -0
  19. data/lib/fuik/notifications.rb +48 -0
  20. data/lib/fuik/routing/provider_constraint.rb +4 -4
  21. data/lib/fuik/test_helpers.rb +33 -0
  22. data/lib/fuik/version.rb +1 -1
  23. data/lib/fuik.rb +9 -0
  24. data/lib/generators/fuik/provider/templates/base.rb.tt +11 -2
  25. data/lib/generators/fuik/provider/templates/github/base.rb.tt +3 -0
  26. data/lib/generators/fuik/provider/templates/loops/base.rb.tt +2 -0
  27. data/lib/generators/fuik/provider/templates/postmark/base.rb.tt +1 -0
  28. data/lib/generators/fuik/provider/templates/spinal/base.rb.tt +16 -0
  29. data/lib/generators/fuik/provider/templates/spinal/collection_synced.rb.tt +14 -0
  30. data/lib/generators/fuik/provider/templates/spinal/resource_created.rb.tt +14 -0
  31. data/lib/generators/fuik/provider/templates/spinal/resource_pushed.rb.tt +13 -0
  32. data/lib/generators/fuik/provider/templates/spinal/resource_scheduled.rb.tt +13 -0
  33. data/lib/generators/fuik/provider/templates/spinal/resource_unscheduled.rb.tt +12 -0
  34. metadata +14 -3
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fuik
4
+ class RetriesController < Fuik.configuration.events_controller_parent.constantize
5
+ def create
6
+ webhook_event = WebhookEvent.find(params[:event_id])
7
+ webhook_event.retry!
8
+
9
+ redirect_to event_path(webhook_event)
10
+ end
11
+ end
12
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fuik
4
- class WebhooksController < Fuik::Engine.config.webhooks_controller_parent.constantize
4
+ class WebhooksController < Fuik.configuration.webhooks_controller_parent.constantize
5
5
  include EventType
6
6
 
7
7
  skip_before_action :verify_authenticity_token
@@ -17,14 +17,29 @@ module Fuik
17
17
  headers: headers
18
18
  )
19
19
 
20
- process_later!(webhook_event)
20
+ Notifications.received(
21
+ provider: params[:provider],
22
+ event_id: event_id,
23
+ event_type: event_type,
24
+ webhook_event: webhook_event
25
+ )
26
+
27
+ webhook_event.process_later!
21
28
 
22
29
  head :ok
23
30
  rescue Fuik::InvalidSignature
31
+ Notifications.signature_invalid(
32
+ provider: params[:provider],
33
+ event_id: event_id
34
+ )
24
35
  head :unauthorized
25
36
  rescue ActiveRecord::RecordNotUnique
26
37
  head :ok
27
- rescue
38
+ rescue => error
39
+ Notifications.receive_error(
40
+ provider: request.path_parameters[:provider],
41
+ error: error
42
+ )
28
43
  head :internal_server_error
29
44
  end
30
45
 
@@ -60,27 +75,16 @@ module Fuik
60
75
  end
61
76
 
62
77
  def process!(webhook_event)
63
- event_class = event_class_for(webhook_event.provider, webhook_event.event_type)
78
+ event_class = WebhookEvent.event_class_for(webhook_event.provider, webhook_event.event_type)
64
79
  return unless event_class
65
80
 
66
81
  event_class.new(webhook_event).process!
67
82
  end
68
83
 
69
- def process_later!(webhook_event)
70
- event_class = event_class_for(webhook_event.provider, webhook_event.event_type)
71
- return unless event_class
72
-
73
- WebhookProcessingJob.perform_later(event_class.name, webhook_event)
74
- end
75
-
76
84
  def url_encoded_body?
77
85
  request.media_type == "application/x-www-form-urlencoded"
78
86
  end
79
87
 
80
- def event_class_for(provider, event_type)
81
- "#{provider.camelize}::#{event_type.tr("./:-[]", "_").camelize}".safe_constantize
82
- end
83
-
84
88
  def should_verify? = base_class&.respond_to?(:verify!)
85
89
 
86
90
  def base_class
@@ -1,7 +1,21 @@
1
1
  module Fuik
2
2
  class WebhookProcessingJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ discard_on ActiveJob::DeserializationError
6
+
3
7
  def perform(event_class_name, webhook_event)
4
8
  Object.const_get(event_class_name).new(webhook_event).process!
9
+ rescue => error
10
+ webhook_event.failed!(error)
11
+
12
+ Notifications.failed(
13
+ webhook_event: webhook_event,
14
+ event_class: event_class_name,
15
+ error: error
16
+ )
17
+
18
+ raise
5
19
  end
6
20
  end
7
21
  end
@@ -4,14 +4,50 @@ module Fuik
4
4
  class Event
5
5
  using DotAccess
6
6
 
7
+ class_attribute :event_type_config, default: nil
8
+ class_attribute :event_id_config, default: nil
9
+
10
+ class << self
11
+ def event_type(key = nil, **options)
12
+ self.event_type_config = key || options
13
+ end
14
+
15
+ def event_id(key = nil, **options)
16
+ self.event_id_config = key || options
17
+ end
18
+ end
19
+
7
20
  def initialize(webhook_event)
8
21
  @webhook_event = webhook_event
9
22
  end
10
23
 
24
+ # Called when the webhook event is processed. Override to handle the event
25
+ # payload and transition the event's status to processed or failed.
26
+ #
27
+ # @return [void]
28
+ #
29
+ # @example Implementing a checkout.session.completed handler
30
+ # def process!
31
+ # User.find_by(id: payload.client_reference_id).tap do |user|
32
+ # user.activate_subscription!
33
+ # user.send_welcome_email
34
+ # end
35
+ #
36
+ # @webhook_event.processed!
37
+ # end
11
38
  def process!
12
39
  raise NotImplementedError, "#{self.class} must implement #process!"
13
40
  end
14
41
 
42
+ # Access the webhook payload with dot notation or standard hash access.
43
+ #
44
+ # @return [Fuik::DotAccess::AccessPayload] the parsed JSON body wrapped in a dot-accessible object
45
+ #
46
+ # @example Dot notation
47
+ # payload.customer.email
48
+ #
49
+ # @example Hash access
50
+ # payload["line_items"][0]["product_id"]
15
51
  def payload
16
52
  @webhook_event.payload.to_dot
17
53
  end
@@ -7,10 +7,9 @@ module Fuik
7
7
 
8
8
  class_methods do
9
9
  def filtered(params)
10
- events = all
11
- events = events.where(status: params[:status]) if params[:status].present?
12
- events = events.where(provider: params[:provider]) if params[:provider].present?
13
- events
10
+ filters = %w[status provider event_id event_type]
11
+
12
+ where(params.permit(*filters).compact_blank.transform_values { it.split(",") })
14
13
  end
15
14
 
16
15
  def options_for_select
@@ -4,6 +4,10 @@ module Fuik
4
4
  class WebhookEvent < ApplicationRecord
5
5
  self.table_name = "fuik_webhook_events"
6
6
 
7
+ def self.event_class_for(provider, event_type)
8
+ "#{provider.camelize}::#{event_type.tr("./:-[]", "_").camelize}".safe_constantize
9
+ end
10
+
7
11
  include Filterable
8
12
 
9
13
  enum :status, %w[pending processed failed].index_by(&:itself), default: "pending"
@@ -17,12 +21,39 @@ module Fuik
17
21
  {}
18
22
  end
19
23
 
24
+ # Mark the event as successfully processed.
25
+ #
26
+ # @return [Boolean] true if the update succeeded
20
27
  def processed!
21
28
  update!(status: "processed")
22
29
  end
23
30
 
24
- def failed!(error)
31
+ # Mark the event as failed and record the error.
32
+ #
33
+ # @param error [String, nil] optional error message or object
34
+ #
35
+ # @return [Boolean] true if the update succeeded
36
+ def failed!(error = nil)
25
37
  update!(status: "failed", error: error.to_s)
26
38
  end
39
+
40
+ # Retry a failed event. Resets its status back to pending, clears the error,
41
+ # and enqueues processing via WebhookProcessingJob.
42
+ #
43
+ # @raise [RuntimeError] if the event is not in a failed state
44
+ #
45
+ # @return [Boolean] true if the update succeeded
46
+ def retry!
47
+ raise "Can only retry failed events" unless failed?
48
+
49
+ update!(status: "pending", error: nil)
50
+ process_later!
51
+ end
52
+
53
+ def process_later!
54
+ event_class = self.class.event_class_for(provider, event_type)
55
+
56
+ Fuik.webhook_processing_job_class.constantize.perform_later(event_class.name, self) if event_class
57
+ end
27
58
  end
28
59
  end
@@ -1,4 +1,4 @@
1
- <h1>Webhooks</h1>
1
+ <h1 class="heading"><%= Fuik.configuration.title %></h1>
2
2
 
3
3
  <%= render "filters" %>
4
4
 
@@ -20,4 +20,8 @@
20
20
  <% end %>
21
21
  </li>
22
22
  <% end %>
23
+
24
+ <li class="empty-state">
25
+ <p>No webhook events…</p>
26
+ </li>
23
27
  </ul>
@@ -1,34 +1,57 @@
1
- <% content_for :title, "#{@webhook_event.provider} / #{@webhook_event.event_type} | Fuik Admin" %>
1
+ <% content_for :title, "#{@webhook_event.provider} / #{@webhook_event.event_type} - #{Fuik.configuration.title}" %>
2
2
 
3
- <h1>
4
- <%= link_to "Webhooks", root_path %> / <%= @webhook_event.event_id %>
3
+ <h1 class="heading">
4
+ <%= link_to Fuik.configuration.title, root_path %> <span class="divider">/</span> <%= @webhook_event.event_id %>
5
5
  </h1>
6
6
 
7
- <dl>
8
- <dt>Provider</dt>
9
- <dd><%= @webhook_event.provider %></dd>
7
+ <dl class="details">
8
+ <dt class="description">Provider</dt>
9
+ <dd class="value">
10
+ <%= fuik_icon @webhook_event.provider %> <%= @webhook_event.provider %>
11
+ </dd>
10
12
 
11
- <dt>Event ID</dt>
12
- <dd><%= tag.code @webhook_event.event_id, id: "event_id" %> <%= render "copy_button", target: "event_id" %></dd>
13
+ <dt class="description">Event ID</dt>
14
+ <dd class="value">
15
+ <%= tag.code @webhook_event.event_id, id: "event_id", data: {action: "copy", copy_delay: 5000} %> <%= render "copy_button", target: "event_id" %>
16
+ </dd>
13
17
 
14
- <dt>Type</dt>
15
- <dd><%= @webhook_event.event_type %></dd>
18
+ <dt class="description">Type</dt>
19
+ <dd class="value">
20
+ <%= @webhook_event.event_type %>
21
+ </dd>
16
22
 
17
- <dt>Status</dt>
18
- <dd class="status" data-status="<%= @webhook_event.status %>"><%= @webhook_event.status %></dd>
23
+ <dt class="description">Status</dt>
24
+ <dd class="value status" data-status="<%= @webhook_event.status %>">
25
+ <%= tag.span @webhook_event.status %>
19
26
 
20
- <dt>Created</dt>
21
- <dd><%= @webhook_event.created_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
27
+ <%= button_to event_retries_path(@webhook_event), method: :post, class: "retry-button" do %>
28
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.6a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0v29.8L60.17,60.29h0A96,96,0,0,1,224,128Z"/></svg>
22
29
 
23
- <dt>Payload</dt>
24
- <dd><pre id="payload" data-root="payload" class="json-highlight"><%= highlighted(JSON.pretty_generate(@webhook_event.payload)) %></pre> <div class="actions"><%= button_to downloads_path, params: { event_id: @webhook_event.id }, method: :post, class: "" do %><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34Zm-56,83.32-24,24a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L120,164.69V120a8,8,0,0,1,16,0v44.69l10.34-10.35a8,8,0,0,1,11.32,11.32ZM152,88V44l44,44Z"/></svg><% end %> <%= render "copy_button", target: "payload" %></div></dd>
30
+ Retry
31
+ <% end if @webhook_event.failed? %>
32
+ </dd>
25
33
 
26
- <dt>Headers</dt>
27
- <dd><pre id="headers" class="json-highlight"><%= highlighted JSON.pretty_generate(@webhook_event.headers) %></pre> <div class="actions"><%= render "copy_button", target: "headers" %></div></dd>
34
+ <dt class="description">Created</dt>
35
+ <dd class="value">
36
+ <%= @webhook_event.created_at.strftime("%Y-%m-%d %H:%M:%S") %>
37
+ </dd>
38
+
39
+ <dt class="description">Payload</dt>
40
+ <dd class="value">
41
+ <pre id="payload" data-root="payload" class="json-highlight"><%= highlighted(JSON.pretty_generate(@webhook_event.payload)) %></pre> <div class="actions"><%= button_to downloads_path, params: { event_id: @webhook_event.id }, method: :post, class: "" do %><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34Zm-56,83.32-24,24a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L120,164.69V120a8,8,0,0,1,16,0v44.69l10.34-10.35a8,8,0,0,1,11.32,11.32ZM152,88V44l44,44Z"/></svg><% end %> <%= render "copy_button", target: "payload" %></div>
42
+ </dd>
43
+
44
+ <dt class="description">Headers</dt>
45
+ <dd class="value">
46
+ <pre id="headers" class="json-highlight"><%= highlighted JSON.pretty_generate(@webhook_event.headers) %></pre> <div class="actions"><%= render "copy_button", target: "headers" %></div>
47
+ </dd>
28
48
 
29
49
  <% if @webhook_event.error.present? %>
30
- <dt>Error</dt>
31
- <dd><%= @webhook_event.error %></dd>
50
+ <dt class="description">Error</dt>
51
+
52
+ <dd class="value errors">
53
+ <%= @webhook_event.error %>
54
+ </dd>
32
55
  <% end %>
33
56
  </dl>
34
57
 
@@ -1,12 +1,22 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title><%= content_for?(:title) ? yield(:title) : "Fuik Admin" %></title>
4
+ <title><%= content_for?(:title) ? yield(:title) : Fuik.configuration.title %></title>
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
7
 
8
8
  <%= yield :head %>
9
9
 
10
+ <style>
11
+ :root {
12
+ color-scheme: <%= {
13
+ auto: "light dark",
14
+ light: "light",
15
+ dark: "dark"
16
+ }.fetch(Fuik.configuration.color_scheme.to_sym) %>;
17
+ }
18
+ </style>
19
+
10
20
  <%= stylesheet_link_tag "fuik/application", media: "all" %>
11
21
  <script src="<%= asset_path("attractivejs-0.12.0.min.js") %>"></script>
12
22
  </head>
data/config/routes.rb CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  Fuik::Engine.routes.draw do
4
4
  root to: "events#index"
5
- resources :events, only: %w[show]
5
+
6
+ resources :events, only: %w[index show] do
7
+ resources :retries, only: %w[create]
8
+ end
6
9
  resources :downloads, only: %w[create]
7
10
 
8
11
  post ":provider", to: "webhooks#create", constraints: Fuik::Routing::ProviderConstraint.new
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fuik
4
+ class Configuration
5
+ attr_accessor :events_controller_parent, :webhooks_controller_parent,
6
+ :providers_allowed, :title, :color_scheme, :webhook_processing_job_class
7
+
8
+ def initialize
9
+ @events_controller_parent = "ActionController::Base"
10
+ @webhooks_controller_parent = "ActionController::Base"
11
+ @webhook_processing_job_class = "Fuik::WebhookProcessingJob"
12
+ @providers_allowed = nil
13
+ @title = "Webhooks"
14
+ @color_scheme = :light
15
+ end
16
+ end
17
+
18
+ class << self
19
+ def configuration
20
+ @configuration ||= Configuration.new
21
+ end
22
+
23
+ def configure
24
+ yield configuration
25
+ end
26
+
27
+ delegate :webhook_processing_job_class, :webhook_processing_job_class=, to: :configuration
28
+ end
29
+ end
data/lib/fuik/engine.rb CHANGED
@@ -6,10 +6,6 @@ module Fuik
6
6
  class Engine < ::Rails::Engine
7
7
  isolate_namespace Fuik
8
8
 
9
- config.webhooks_controller_parent = "ActionController::Base"
10
- config.events_controller_parent = "ActionController::Base"
11
- config.providers_allowed = Rails.env.local?
12
-
13
9
  config.to_prepare do
14
10
  ActiveSupport.on_load(:action_view) do
15
11
  include Fuik::IconHelper
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fuik
4
+ class InvalidSignature < StandardError; end
5
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fuik
4
+ module Notifications
5
+ class << self
6
+ def received(provider:, event_id:, event_type:, webhook_event:)
7
+ publish("webhook_received",
8
+ provider: provider,
9
+ event_id: event_id,
10
+ event_type: event_type,
11
+ webhook_event: webhook_event)
12
+ end
13
+
14
+ def processed(webhook_event:, event_class:)
15
+ publish("webhook_processed",
16
+ provider: webhook_event.provider,
17
+ event_id: webhook_event.event_id,
18
+ event_type: webhook_event.event_type,
19
+ event_class: event_class,
20
+ webhook_event: webhook_event)
21
+ end
22
+
23
+ def failed(webhook_event:, event_class:, error:)
24
+ publish("webhook_failed",
25
+ provider: webhook_event.provider,
26
+ event_id: webhook_event.event_id,
27
+ event_type: webhook_event.event_type,
28
+ event_class: event_class,
29
+ error: error,
30
+ webhook_event: webhook_event)
31
+ end
32
+
33
+ def signature_invalid(provider:, event_id:)
34
+ publish("webhook_signature_invalid", provider: provider, event_id: event_id)
35
+ end
36
+
37
+ def receive_error(provider:, error:)
38
+ publish("webhook_receive_error", provider: provider, error: error)
39
+ end
40
+
41
+ private
42
+
43
+ def publish(name, payload = {})
44
+ ActiveSupport::Notifications.publish("#{name}.fuik", payload)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -15,13 +15,13 @@ module Fuik
15
15
 
16
16
  private
17
17
 
18
- def allow_all? = Fuik::Engine.config.providers_allowed.in?([:all, "all"])
18
+ def allow_all? = Fuik.configuration.providers_allowed.in?([:all, "all"])
19
19
 
20
- def explicit_allowlist? = Fuik::Engine.config.providers_allowed.is_a?(Array)
20
+ def explicit_allowlist? = Fuik.configuration.providers_allowed.is_a?(Array)
21
21
 
22
- def explicit_allowlist = Fuik::Engine.config.providers_allowed.to_set
22
+ def explicit_allowlist = Fuik.configuration.providers_allowed.to_set
23
23
 
24
- def providers_allowed = Fuik::Engine.config.providers_allowed
24
+ def providers_allowed = Fuik.configuration.providers_allowed
25
25
 
26
26
  def scanned_allowlist
27
27
  @scanned_allowlist ||= Dir["#{Rails.root}/app/webhooks/*"]
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fuik
4
+ module TestHelpers
5
+ def build_webhook_event(provider: "test", event_id: "test_123", event_type: "test", payload: {})
6
+ WebhookEventDouble.new(provider, event_id, event_type, payload)
7
+ end
8
+
9
+ class WebhookEventDouble
10
+ attr_reader :provider, :event_id, :event_type, :status, :error
11
+
12
+ def initialize(provider, event_id, event_type, payload)
13
+ @provider = provider
14
+ @event_id = event_id
15
+ @event_type = event_type
16
+ @payload = payload
17
+ end
18
+
19
+ def payload
20
+ @payload.is_a?(Hash) ? @payload : {}
21
+ end
22
+
23
+ def processed!
24
+ @status = "processed"
25
+ end
26
+
27
+ def failed!(error)
28
+ @status = "failed"
29
+ @error = error.to_s
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/fuik/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fuik
2
- VERSION = "0.12.0"
2
+ VERSION = "0.13.0"
3
3
  end
data/lib/fuik.rb CHANGED
@@ -1,7 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "fuik/version"
4
+ require "fuik/errors"
5
+ require "fuik/configuration"
2
6
  require "fuik/dot_access"
7
+ require "fuik/notifications"
3
8
  require "fuik/engine"
4
9
 
5
10
  module Fuik
6
11
  class InvalidSignature < StandardError; end
12
+
13
+ def self.deprecator
14
+ @deprecator ||= ActiveSupport::Deprecation.new("1.0", "Fuik")
15
+ end
7
16
  end
@@ -1,8 +1,17 @@
1
1
  module <%= provider_module_name %>
2
2
  class Base < Fuik::Event
3
3
  # def self.verify!(request)
4
- # TODO: Implement signature verification
5
- # raise Fuik::InvalidSignature # only raise when signature is invalid
4
+ # # TODO: Implement signature verification
5
+ # # raise Fuik::InvalidSignature # only raise when signature is invalid
6
6
  # end
7
+
8
+ # Override if Fuik can't find the event type or id on its own:
9
+ #
10
+ # event_type header: "X-Custom-Event"
11
+ # event_type payload: "event_type"
12
+ # event_type "always_this_value"
13
+ #
14
+ # event_id header: "X-Custom-Id"
15
+ # event_id payload: "id"
7
16
  end
8
17
  end
@@ -1,5 +1,8 @@
1
1
  module <%= provider_module_name %>
2
2
  class Base < Fuik::Event
3
+ event_type header: "X-Github-Event"
4
+ event_id header: "X-GitHub-Delivery"
5
+
3
6
  def self.verify!(request)
4
7
  secret = Rails.application.credentials.dig(:github, :secret)
5
8
  signature = request.headers["X-Hub-Signature-256"]
@@ -1,5 +1,7 @@
1
1
  module <%= provider_module_name %>
2
2
  class Base < Fuik::Event
3
+ event_id header: "webhook-id"
4
+
3
5
  def self.verify!(request)
4
6
  secret = Rails.application.credentials.dig(:loops, :signing_secret)
5
7
  secret_bytes = Base64.strict_decode64(secret.split("_")[1])
@@ -1,4 +1,5 @@
1
1
  module <%= provider_module_name %>
2
2
  class Base < Fuik::Event
3
+ event_type header: "X-Postmark-Event"
3
4
  end
4
5
  end
@@ -0,0 +1,16 @@
1
+ module <%= provider_module_name %>
2
+ class Base < Fuik::Event
3
+ event_type payload: "type"
4
+ event_id payload: "data.id"
5
+
6
+ # def self.verify!(request)
7
+ # secret = Rails.application.credentials.dig(:spinal, :webhook_secret)
8
+ # signature = request.headers["webhook-signature"]
9
+ # id = request.headers["webhook-id"]
10
+ # timestamp = request.headers["webhook-timestamp"]
11
+
12
+ # expected = OpenSSL::HMAC.base64digest("SHA256", secret, "#{id}.#{timestamp}.#{request.raw_post}")
13
+ # raise Fuik::InvalidSignature unless Rack::Utils.secure_compare(signature, "v1,#{expected}")
14
+ # end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ module <%= provider_module_name %>
2
+ class CollectionSynced < Base
3
+ def process!
4
+ # payload["data"]["id"] # collection id
5
+ # payload["data"]["stats"]["added"] # resources added during sync
6
+ # payload["data"]["stats"]["updated"] # resources updated during sync
7
+ # payload["data"]["stats"]["removed"] # resources removed during sync
8
+
9
+ # TODO: Add business logic
10
+
11
+ @webhook_event.processed!
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module <%= provider_module_name %>
2
+ class ResourceCreated < Base
3
+ def process!
4
+ # payload["data"]["id"] # resource id
5
+ # payload["data"]["filename"] # e.g., "posts/hello-world.md"
6
+ # payload["data"]["relative_path"] # e.g., "posts/hello-world"
7
+ # payload["data"]["status"] # "draft" | "published"
8
+
9
+ # TODO: Add business logic
10
+
11
+ @webhook_event.processed!
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module <%= provider_module_name %>
2
+ class ResourcePushed < Base
3
+ def process!
4
+ # payload["data"]["id"] # resource id
5
+ # payload["data"]["filename"] # e.g., "posts/hello-world.md"
6
+ # payload["data"]["status"] # "draft" | "published"
7
+
8
+ # TODO: Add business logic
9
+
10
+ @webhook_event.processed!
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module <%= provider_module_name %>
2
+ class ResourceScheduled < Base
3
+ def process!
4
+ # payload["data"]["id"] # resource id
5
+ # payload["data"]["filename"] # e.g., "posts/hello-world.md"
6
+ # payload["data"]["published_at"] # ISO8601 scheduled publish time
7
+
8
+ # TODO: Add business logic
9
+
10
+ @webhook_event.processed!
11
+ end
12
+ end
13
+ end