webhooks-rails 0.1.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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +20 -0
  5. data/app/assets/config/webhooks_manifest.js +1 -0
  6. data/app/assets/stylesheets/webhooks/application.css +15 -0
  7. data/app/controllers/webhooks/application_controller.rb +6 -0
  8. data/app/controllers/webhooks/attempts_controller.rb +25 -0
  9. data/app/controllers/webhooks/endpoints_controller.rb +65 -0
  10. data/app/controllers/webhooks/events_controller.rb +37 -0
  11. data/app/helpers/webhooks/application_helper.rb +6 -0
  12. data/app/jobs/webhooks/application_job.rb +6 -0
  13. data/app/jobs/webhooks/deliver_job.rb +11 -0
  14. data/app/mailers/webhooks/application_mailer.rb +8 -0
  15. data/app/models/webhooks/application_record.rb +7 -0
  16. data/app/models/webhooks/attempt.rb +71 -0
  17. data/app/models/webhooks/endpoint.rb +74 -0
  18. data/app/models/webhooks/event.rb +50 -0
  19. data/app/models/webhooks/event_serializer.rb +30 -0
  20. data/app/models/webhooks/request.rb +37 -0
  21. data/app/models/webhooks/response.rb +29 -0
  22. data/app/views/layouts/webhooks/application.html.erb +29 -0
  23. data/app/views/webhooks/attempts/_attempt.html.erb +32 -0
  24. data/app/views/webhooks/attempts/show.html.erb +33 -0
  25. data/app/views/webhooks/endpoints/_endpoint.html.erb +31 -0
  26. data/app/views/webhooks/endpoints/_form.html.erb +42 -0
  27. data/app/views/webhooks/endpoints/edit.html.erb +16 -0
  28. data/app/views/webhooks/endpoints/index.html.erb +41 -0
  29. data/app/views/webhooks/endpoints/new.html.erb +16 -0
  30. data/app/views/webhooks/endpoints/show.html.erb +102 -0
  31. data/app/views/webhooks/events/_event.html.erb +11 -0
  32. data/app/views/webhooks/events/index.html.erb +38 -0
  33. data/app/views/webhooks/events/new.html.erb +35 -0
  34. data/app/views/webhooks/events/show.html.erb +49 -0
  35. data/app/views/webhooks/requests/_request.html.erb +47 -0
  36. data/app/views/webhooks/responses/_response.html.erb +55 -0
  37. data/app/views/webhooks/shared/_navigation.html.erb +100 -0
  38. data/config/routes.rb +13 -0
  39. data/db/migrate/20210518022959_create_webhooks_endpoints.rb +17 -0
  40. data/db/migrate/20210518043350_create_webhooks_events.rb +12 -0
  41. data/db/migrate/20210518050123_create_webhooks_attempts.rb +36 -0
  42. data/db/migrate/20210518054916_create_webhooks_requests.rb +14 -0
  43. data/db/migrate/20210518060614_create_webhooks_responses.rb +15 -0
  44. data/lib/tasks/webhooks_tasks.rake +5 -0
  45. data/lib/webhooks-rails.rb +3 -0
  46. data/lib/webhooks.rb +21 -0
  47. data/lib/webhooks/engine.rb +31 -0
  48. data/lib/webhooks/version.rb +5 -0
  49. 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