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.
- checksums.yaml +4 -4
- data/README.md +156 -25
- data/app/assets/stylesheets/fuik/application.css +152 -88
- data/app/controllers/concerns/fuik/event_type.rb +32 -7
- data/app/controllers/fuik/events_controller.rb +6 -1
- data/app/controllers/fuik/retries_controller.rb +12 -0
- data/app/controllers/fuik/webhooks_controller.rb +19 -15
- data/app/jobs/fuik/webhook_processing_job.rb +14 -0
- data/app/models/fuik/event.rb +36 -0
- data/app/models/fuik/webhook_event/filterable.rb +3 -4
- data/app/models/fuik/webhook_event.rb +32 -1
- data/app/views/fuik/events/index.html.erb +5 -1
- data/app/views/fuik/events/show.html.erb +43 -20
- data/app/views/layouts/fuik/application.html.erb +11 -1
- data/config/routes.rb +4 -1
- data/lib/fuik/configuration.rb +29 -0
- data/lib/fuik/engine.rb +0 -4
- data/lib/fuik/errors.rb +5 -0
- data/lib/fuik/notifications.rb +48 -0
- data/lib/fuik/routing/provider_constraint.rb +4 -4
- data/lib/fuik/test_helpers.rb +33 -0
- data/lib/fuik/version.rb +1 -1
- data/lib/fuik.rb +9 -0
- data/lib/generators/fuik/provider/templates/base.rb.tt +11 -2
- data/lib/generators/fuik/provider/templates/github/base.rb.tt +3 -0
- data/lib/generators/fuik/provider/templates/loops/base.rb.tt +2 -0
- data/lib/generators/fuik/provider/templates/postmark/base.rb.tt +1 -0
- data/lib/generators/fuik/provider/templates/spinal/base.rb.tt +16 -0
- data/lib/generators/fuik/provider/templates/spinal/collection_synced.rb.tt +14 -0
- data/lib/generators/fuik/provider/templates/spinal/resource_created.rb.tt +14 -0
- data/lib/generators/fuik/provider/templates/spinal/resource_pushed.rb.tt +13 -0
- data/lib/generators/fuik/provider/templates/spinal/resource_scheduled.rb.tt +13 -0
- data/lib/generators/fuik/provider/templates/spinal/resource_unscheduled.rb.tt +12 -0
- 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
|
|
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
|
-
|
|
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
|
data/app/models/fuik/event.rb
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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,34 +1,57 @@
|
|
|
1
|
-
<% content_for :title, "#{@webhook_event.provider} / #{@webhook_event.event_type}
|
|
1
|
+
<% content_for :title, "#{@webhook_event.provider} / #{@webhook_event.event_type} - #{Fuik.configuration.title}" %>
|
|
2
2
|
|
|
3
|
-
<h1>
|
|
4
|
-
<%= link_to
|
|
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
|
|
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
|
|
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
|
|
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 %>"
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
30
|
+
Retry
|
|
31
|
+
<% end if @webhook_event.failed? %>
|
|
32
|
+
</dd>
|
|
25
33
|
|
|
26
|
-
<dt>
|
|
27
|
-
<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
|
-
|
|
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) :
|
|
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
|
-
|
|
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
|
data/lib/fuik/errors.rb
ADDED
|
@@ -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
|
|
18
|
+
def allow_all? = Fuik.configuration.providers_allowed.in?([:all, "all"])
|
|
19
19
|
|
|
20
|
-
def explicit_allowlist? = Fuik
|
|
20
|
+
def explicit_allowlist? = Fuik.configuration.providers_allowed.is_a?(Array)
|
|
21
21
|
|
|
22
|
-
def explicit_allowlist = Fuik
|
|
22
|
+
def explicit_allowlist = Fuik.configuration.providers_allowed.to_set
|
|
23
23
|
|
|
24
|
-
def providers_allowed = Fuik
|
|
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
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
|
-
|
|
5
|
-
|
|
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"]
|
|
@@ -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
|