webhooks-rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +20 -0
- data/app/assets/config/webhooks_manifest.js +1 -0
- data/app/assets/stylesheets/webhooks/application.css +15 -0
- data/app/controllers/webhooks/application_controller.rb +6 -0
- data/app/controllers/webhooks/attempts_controller.rb +25 -0
- data/app/controllers/webhooks/endpoints_controller.rb +65 -0
- data/app/controllers/webhooks/events_controller.rb +37 -0
- data/app/helpers/webhooks/application_helper.rb +6 -0
- data/app/jobs/webhooks/application_job.rb +6 -0
- data/app/jobs/webhooks/deliver_job.rb +11 -0
- data/app/mailers/webhooks/application_mailer.rb +8 -0
- data/app/models/webhooks/application_record.rb +7 -0
- data/app/models/webhooks/attempt.rb +71 -0
- data/app/models/webhooks/endpoint.rb +74 -0
- data/app/models/webhooks/event.rb +50 -0
- data/app/models/webhooks/event_serializer.rb +30 -0
- data/app/models/webhooks/request.rb +37 -0
- data/app/models/webhooks/response.rb +29 -0
- data/app/views/layouts/webhooks/application.html.erb +29 -0
- data/app/views/webhooks/attempts/_attempt.html.erb +32 -0
- data/app/views/webhooks/attempts/show.html.erb +33 -0
- data/app/views/webhooks/endpoints/_endpoint.html.erb +31 -0
- data/app/views/webhooks/endpoints/_form.html.erb +42 -0
- data/app/views/webhooks/endpoints/edit.html.erb +16 -0
- data/app/views/webhooks/endpoints/index.html.erb +41 -0
- data/app/views/webhooks/endpoints/new.html.erb +16 -0
- data/app/views/webhooks/endpoints/show.html.erb +102 -0
- data/app/views/webhooks/events/_event.html.erb +11 -0
- data/app/views/webhooks/events/index.html.erb +38 -0
- data/app/views/webhooks/events/new.html.erb +35 -0
- data/app/views/webhooks/events/show.html.erb +49 -0
- data/app/views/webhooks/requests/_request.html.erb +47 -0
- data/app/views/webhooks/responses/_response.html.erb +55 -0
- data/app/views/webhooks/shared/_navigation.html.erb +100 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20210518022959_create_webhooks_endpoints.rb +17 -0
- data/db/migrate/20210518043350_create_webhooks_events.rb +12 -0
- data/db/migrate/20210518050123_create_webhooks_attempts.rb +36 -0
- data/db/migrate/20210518054916_create_webhooks_requests.rb +14 -0
- data/db/migrate/20210518060614_create_webhooks_responses.rb +15 -0
- data/lib/tasks/webhooks_tasks.rake +5 -0
- data/lib/webhooks-rails.rb +3 -0
- data/lib/webhooks.rb +21 -0
- data/lib/webhooks/engine.rb +31 -0
- data/lib/webhooks/version.rb +5 -0
- metadata +183 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
<tr>
|
2
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
3
|
+
<%= event %>
|
4
|
+
</td>
|
5
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
6
|
+
<%= event.event %>
|
7
|
+
</td>
|
8
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-right">
|
9
|
+
<%= link_to event.created_at, event %>
|
10
|
+
</td>
|
11
|
+
</tr>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<div class="py-6">
|
2
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8 md:flex md:justify-between md:space-x-5">
|
3
|
+
<h1 class="text-2xl font-semibold text-gray-900">Events</h1>
|
4
|
+
<div class="mt-6 flex flex-col-reverse justify-stretch space-y-4 space-y-reverse sm:flex-row-reverse sm:justify-end sm:space-x-reverse sm:space-y-0 sm:space-x-3 md:mt-0 md:flex-row md:space-x-3">
|
5
|
+
<%= link_to 'New Event', new_event_path, class: 'inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blue-500' %>
|
6
|
+
</div>
|
7
|
+
</div>
|
8
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
|
9
|
+
<div class="py-4">
|
10
|
+
<div class="flex flex-col">
|
11
|
+
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
12
|
+
<div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
13
|
+
<div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
|
14
|
+
<table class="min-w-full divide-y divide-gray-200">
|
15
|
+
<thead class="bg-gray-50">
|
16
|
+
<tr>
|
17
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
18
|
+
Event Type
|
19
|
+
</th>
|
20
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
21
|
+
Event
|
22
|
+
</th>
|
23
|
+
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
24
|
+
Timestamp
|
25
|
+
</th>
|
26
|
+
</tr>
|
27
|
+
</thead>
|
28
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
29
|
+
<%= render @events %>
|
30
|
+
</tbody>
|
31
|
+
</table>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</div>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<div class="py-6">
|
2
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
|
3
|
+
<h1 class="text-2xl font-semibold text-gray-900">New Event</h1>
|
4
|
+
</div>
|
5
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
|
6
|
+
<div class="py-4">
|
7
|
+
<div class="flex flex-col">
|
8
|
+
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
9
|
+
<div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
10
|
+
<%= form_with model: @event do |form| %>
|
11
|
+
<div class="space-y-6">
|
12
|
+
<div>
|
13
|
+
<%= form.label :event_type, class: 'block text-sm font-medium text-gray-700' %>
|
14
|
+
<%= form.collection_select :event_type, Rails.application.config.webhooks.events.types, :to_s, :to_s, prompt: true, class: "select" %>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div>
|
18
|
+
<%= form.label :event, class: 'block text-sm font-medium text-gray-700' %>
|
19
|
+
<div class="mt-1">
|
20
|
+
<%= form.text_area :event, value: '{"action":"user.created"}', rows: 25, class: 'block w-full shadow-sm focus:ring-light-blue-500 focus:border-light-blue-500 sm:text-sm border-gray-300 rounded-md py-2 px-4 font-mono', placeholder: 'https://example.com/webhook' %>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="flex justify-end">
|
25
|
+
<%= link_to 'Cancel', events_path, class: 'bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500' %>
|
26
|
+
<%= form.submit 'Create Event', class: 'cursor-pointer ml-3 inline-flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500' %>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
<% end %>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</div>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
<div class="py-6">
|
2
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8 md:flex md:justify-between md:space-x-5">
|
3
|
+
<h1 class="text-2xl font-semibold text-gray-900"><%= @event %></h1>
|
4
|
+
</div>
|
5
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
|
6
|
+
<div class="py-4">
|
7
|
+
<div class="flex flex-col">
|
8
|
+
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
9
|
+
<div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8 space-y-6">
|
10
|
+
<pre><%= @event.event %></pre>
|
11
|
+
|
12
|
+
<div>
|
13
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
14
|
+
Attempts
|
15
|
+
</h3>
|
16
|
+
<p class="mt-2 max-w-4xl text-sm text-gray-500">
|
17
|
+
All webhook delivery attempts and their status.
|
18
|
+
</p>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
|
22
|
+
<table class="min-w-full divide-y divide-gray-200">
|
23
|
+
<thead class="bg-gray-50">
|
24
|
+
<tr>
|
25
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
26
|
+
Status
|
27
|
+
</th>
|
28
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
29
|
+
Event Type
|
30
|
+
</th>
|
31
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
32
|
+
Endpoint
|
33
|
+
</th>
|
34
|
+
<th scope="col" class="relative px-6 py-3">
|
35
|
+
<span class="sr-only">Edit</span>
|
36
|
+
</th>
|
37
|
+
</tr>
|
38
|
+
</thead>
|
39
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
40
|
+
<%= render @event.attempts.order(created_at: :desc) %>
|
41
|
+
</tbody>
|
42
|
+
</table>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
</div>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<div class="space-y-6">
|
2
|
+
<div>
|
3
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
4
|
+
Request
|
5
|
+
</h3>
|
6
|
+
<p class="mt-2 max-w-4xl text-sm text-gray-500">
|
7
|
+
The request delivered to <%= @attempt.endpoint %>
|
8
|
+
</p>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
|
12
|
+
<table class="min-w-full divide-y divide-gray-200">
|
13
|
+
<thead class="bg-gray-50">
|
14
|
+
<tr>
|
15
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
16
|
+
Header
|
17
|
+
</th>
|
18
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
19
|
+
Value
|
20
|
+
</th>
|
21
|
+
</tr>
|
22
|
+
</thead>
|
23
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
24
|
+
<% request.headers.each do |key, value| %>
|
25
|
+
<tr>
|
26
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
27
|
+
<%= key %>
|
28
|
+
</td>
|
29
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
30
|
+
<%= value %>
|
31
|
+
</td>
|
32
|
+
</tr>
|
33
|
+
<% end %>
|
34
|
+
</tbody>
|
35
|
+
</table>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
<div>
|
39
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
40
|
+
Request Body
|
41
|
+
</h3>
|
42
|
+
<p class="mt-2 max-w-4xl text-sm text-gray-500">
|
43
|
+
The request delivered to <%= @attempt.endpoint %>
|
44
|
+
</p>
|
45
|
+
</div>
|
46
|
+
<div class="bg-white shadow overflow-hidden border-b border-gray-200 sm:rounded-lg px-6 py-3"><pre><%= request.body %></pre></div>
|
47
|
+
</div>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<div class="space-y-6">
|
2
|
+
<div>
|
3
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
4
|
+
Response
|
5
|
+
</h3>
|
6
|
+
<p class="mt-2 max-w-4xl text-sm text-gray-500">
|
7
|
+
The response headers received from <%= response.endpoint %>
|
8
|
+
</p>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
|
12
|
+
<table class="min-w-full divide-y divide-gray-200">
|
13
|
+
<thead class="bg-gray-50">
|
14
|
+
<tr>
|
15
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
16
|
+
Header
|
17
|
+
</th>
|
18
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
19
|
+
Value
|
20
|
+
</th>
|
21
|
+
</tr>
|
22
|
+
</thead>
|
23
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
24
|
+
<tr>
|
25
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
26
|
+
Status
|
27
|
+
</td>
|
28
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
29
|
+
<%= response.status_code %>
|
30
|
+
</td>
|
31
|
+
</tr>
|
32
|
+
<% response.headers.each do |key, value| %>
|
33
|
+
<tr>
|
34
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
35
|
+
<%= key %>
|
36
|
+
</td>
|
37
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
38
|
+
<%= value %>
|
39
|
+
</td>
|
40
|
+
</tr>
|
41
|
+
<% end %>
|
42
|
+
</tbody>
|
43
|
+
</table>
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div>
|
47
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
48
|
+
Response Body
|
49
|
+
</h3>
|
50
|
+
<p class="mt-2 max-w-4xl text-sm text-gray-500">
|
51
|
+
The response body received from <%= response.endpoint %>
|
52
|
+
</p>
|
53
|
+
</div>
|
54
|
+
<div class="bg-white shadow overflow-hidden border-b border-gray-200 sm:rounded-lg px-6 py-3"><pre><%= response.body %></pre></div>
|
55
|
+
</div>
|
@@ -0,0 +1,100 @@
|
|
1
|
+
<!-- Off-canvas menu for mobile, show/hide based on off-canvas menu state. -->
|
2
|
+
<div class="fixed inset-0 flex z-40 md:hidden" role="dialog" aria-modal="true">
|
3
|
+
<!--
|
4
|
+
Off-canvas menu overlay, show/hide based on off-canvas menu state.
|
5
|
+
|
6
|
+
Entering: "transition-opacity ease-linear duration-300"
|
7
|
+
From: "opacity-0"
|
8
|
+
To: "opacity-100"
|
9
|
+
Leaving: "transition-opacity ease-linear duration-300"
|
10
|
+
From: "opacity-100"
|
11
|
+
To: "opacity-0"
|
12
|
+
-->
|
13
|
+
<div class="fixed inset-0 bg-gray-600 bg-opacity-75" aria-hidden="true"></div>
|
14
|
+
|
15
|
+
<!--
|
16
|
+
Off-canvas menu, show/hide based on off-canvas menu state.
|
17
|
+
|
18
|
+
Entering: "transition ease-in-out duration-300 transform"
|
19
|
+
From: "-translate-x-full"
|
20
|
+
To: "translate-x-0"
|
21
|
+
Leaving: "transition ease-in-out duration-300 transform"
|
22
|
+
From: "translate-x-0"
|
23
|
+
To: "-translate-x-full"
|
24
|
+
-->
|
25
|
+
<div class="relative flex-1 flex flex-col max-w-xs w-full bg-white">
|
26
|
+
<!--
|
27
|
+
Close button, show/hide based on off-canvas menu state.
|
28
|
+
|
29
|
+
Entering: "ease-in-out duration-300"
|
30
|
+
From: "opacity-0"
|
31
|
+
To: "opacity-100"
|
32
|
+
Leaving: "ease-in-out duration-300"
|
33
|
+
From: "opacity-100"
|
34
|
+
To: "opacity-0"
|
35
|
+
-->
|
36
|
+
<div class="absolute top-0 right-0 -mr-12 pt-2">
|
37
|
+
<button class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
38
|
+
<span class="sr-only">Close sidebar</span>
|
39
|
+
<!-- Heroicon name: outline/x -->
|
40
|
+
<svg class="h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
41
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
42
|
+
</svg>
|
43
|
+
</button>
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div class="flex-1 h-0 pt-5 pb-4 overflow-y-auto">
|
47
|
+
<div class="flex-shrink-0 flex items-center px-4">
|
48
|
+
Webhooks
|
49
|
+
</div>
|
50
|
+
<nav class="mt-5 px-2 space-y-1">
|
51
|
+
<%= link_to endpoints_path, class: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-base font-medium rounded-md' do %>
|
52
|
+
<svg class="text-gray-500 mr-4 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
53
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
54
|
+
</svg>
|
55
|
+
Endpoints
|
56
|
+
<% end %>
|
57
|
+
|
58
|
+
<%= link_to events_path, class: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-base font-medium rounded-md' do %>
|
59
|
+
<svg class="text-gray-500 mr-4 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
60
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
61
|
+
</svg>
|
62
|
+
Events
|
63
|
+
<% end %>
|
64
|
+
</nav>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
|
68
|
+
<div class="flex-shrink-0 w-14">
|
69
|
+
<!-- Force sidebar to shrink to fit close icon -->
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
|
73
|
+
<!-- Static sidebar for desktop -->
|
74
|
+
<div class="hidden md:flex md:flex-shrink-0">
|
75
|
+
<div class="flex flex-col w-64">
|
76
|
+
<!-- Sidebar component, swap this element with another sidebar if you like -->
|
77
|
+
<div class="flex flex-col h-0 flex-1 border-r border-gray-200 bg-white">
|
78
|
+
<div class="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
|
79
|
+
<div class="flex items-center flex-shrink-0 px-4">
|
80
|
+
Webhooks
|
81
|
+
</div>
|
82
|
+
<nav class="mt-5 flex-1 px-2 bg-white space-y-1">
|
83
|
+
<%= link_to endpoints_path, class: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md' do %>
|
84
|
+
<svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
85
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
86
|
+
</svg>
|
87
|
+
Endpoints
|
88
|
+
<% end %>
|
89
|
+
|
90
|
+
<%= link_to events_path, class: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md' do %>
|
91
|
+
<svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
92
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
93
|
+
</svg>
|
94
|
+
Events
|
95
|
+
<% end %>
|
96
|
+
</nav>
|
97
|
+
</div>
|
98
|
+
</div>
|
99
|
+
</div>
|
100
|
+
</div>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Webhooks::Engine.routes.draw do
|
4
|
+
resources :endpoints
|
5
|
+
resources :events, only: %i[index new show create]
|
6
|
+
resources :attempts, only: :show do
|
7
|
+
member do
|
8
|
+
post :reattempt
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
get '/' => redirect('/endpoints')
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateWebhooksEndpoints < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
create_table :webhooks_endpoints do |t|
|
6
|
+
t.string :url, null: false, limit: 256
|
7
|
+
t.string :event_types, array: true, null: true
|
8
|
+
t.integer :state, null: false, default: 0
|
9
|
+
t.string :secret, null: false
|
10
|
+
|
11
|
+
t.timestamps null: false, precision: 6
|
12
|
+
|
13
|
+
t.index :url, unique: true
|
14
|
+
t.index :secret, unique: true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateWebhooksEvents < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
create_table :webhooks_events do |t|
|
6
|
+
t.string :event_type, null: false
|
7
|
+
t.json :event, null: false, default: {}
|
8
|
+
|
9
|
+
t.timestamps null: false, precision: 6
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateWebhooksAttempts < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
reversible do |direction|
|
6
|
+
direction.up do
|
7
|
+
execute <<-SQL.squish
|
8
|
+
DROP INDEX IF EXISTS index_webhooks_attempts_on_endpoint_id_and_event_id_and_attempt;
|
9
|
+
SQL
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table :webhooks_attempts do |t|
|
14
|
+
t.belongs_to :webhooks_endpoint, null: false, foreign_key: true
|
15
|
+
t.belongs_to :webhooks_event, null: false, foreign_key: true
|
16
|
+
|
17
|
+
# Was this attempt triggered manually?
|
18
|
+
t.boolean :manual, null: false, default: false
|
19
|
+
t.integer :state, null: false, default: 0
|
20
|
+
t.integer :attempt, null: false, default: 1
|
21
|
+
|
22
|
+
t.datetime :retry_at, null: true, precision: 6
|
23
|
+
t.timestamps null: false, precision: 6
|
24
|
+
|
25
|
+
t.index %i[webhooks_endpoint_id webhooks_event_id attempt], unique: true, name: 'index_webhooks_attempts_on_endpoint_id_and_event_id_and_attempt'
|
26
|
+
end
|
27
|
+
|
28
|
+
reversible do |direction|
|
29
|
+
direction.up do
|
30
|
+
execute <<-SQL.squish
|
31
|
+
CREATE UNIQUE INDEX index_webhooks_attempts_on_endpoint_id_and_event_id_and_attempt ON webhooks_attempts ("webhooks_endpoint_id", "webhooks_event_id", "attempt") WHERE "manual" = 'f';
|
32
|
+
SQL
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateWebhooksRequests < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
create_table :webhooks_requests do |t|
|
6
|
+
t.belongs_to :webhooks_attempt, null: false, foreign_key: true, index: { unique: true }
|
7
|
+
|
8
|
+
t.json :headers, null: false
|
9
|
+
t.json :body, null: false
|
10
|
+
|
11
|
+
t.datetime :created_at, null: false, precision: 6
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|