webhookdb 1.2.2 → 1.3.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.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/admin-dist/assets/index-6aebf805.js +264 -0
  3. data/admin-dist/favicon.ico +0 -0
  4. data/admin-dist/index.html +130 -0
  5. data/admin-dist/manifest.json +15 -0
  6. data/data/messages/replicators/url-recorder.liquid +20 -0
  7. data/data/messages/templates/errors/signalwire_send_sms.email.liquid +31 -0
  8. data/data/messages/web/install-customer-login.liquid +6 -5
  9. data/data/messages/web/install-error.liquid +1 -1
  10. data/data/messages/web/install-forbidden.liquid +25 -0
  11. data/data/messages/web/install-org-chooser.liquid +40 -0
  12. data/data/messages/web/install-success.liquid +2 -1
  13. data/data/messages/web/install.liquid +2 -1
  14. data/data/messages/web/partials/head.liquid +2 -0
  15. data/data/messages/web/styles.liquid +24 -0
  16. data/db/migrations/041_views.rb +20 -0
  17. data/db/migrations/042_sint_lock.rb +10 -0
  18. data/db/migrations/043_text_search.rb +28 -0
  19. data/db/migrations/044_oauth_session_token_cache.rb +21 -0
  20. data/integration/auth_spec.rb +2 -2
  21. data/lib/sequel/plugins/text_searchable.rb +165 -0
  22. data/lib/sequel/text_searchable.rb +42 -0
  23. data/lib/webhookdb/admin_api/auth.rb +24 -3
  24. data/lib/webhookdb/admin_api/data_provider.rb +196 -0
  25. data/lib/webhookdb/admin_api/entities.rb +143 -28
  26. data/lib/webhookdb/admin_api.rb +0 -2
  27. data/lib/webhookdb/api/auth.rb +5 -6
  28. data/lib/webhookdb/api/db.rb +31 -6
  29. data/lib/webhookdb/api/entities.rb +7 -1
  30. data/lib/webhookdb/api/helpers.rb +6 -25
  31. data/lib/webhookdb/api/install.rb +204 -79
  32. data/lib/webhookdb/api/organizations.rb +14 -12
  33. data/lib/webhookdb/api/saved_queries.rb +9 -3
  34. data/lib/webhookdb/api/saved_views.rb +99 -0
  35. data/lib/webhookdb/api/service_integrations.rb +15 -9
  36. data/lib/webhookdb/api/subscriptions.rb +3 -1
  37. data/lib/webhookdb/api/sync_targets.rb +9 -7
  38. data/lib/webhookdb/api/system.rb +1 -0
  39. data/lib/webhookdb/api/webhook_subscriptions.rb +3 -1
  40. data/lib/webhookdb/apps.rb +30 -7
  41. data/lib/webhookdb/async/audit_logger.rb +2 -0
  42. data/lib/webhookdb/async.rb +5 -0
  43. data/lib/webhookdb/backfill_job/service_integration_lock.rb +22 -0
  44. data/lib/webhookdb/backfill_job.rb +9 -0
  45. data/lib/webhookdb/customer.rb +5 -0
  46. data/lib/webhookdb/database_document.rb +1 -1
  47. data/lib/webhookdb/db_adapter/default_sql.rb +1 -1
  48. data/lib/webhookdb/db_adapter.rb +20 -4
  49. data/lib/webhookdb/fixtures/message_bodies.rb +34 -0
  50. data/lib/webhookdb/fixtures/organizations.rb +5 -0
  51. data/lib/webhookdb/fixtures/roles.rb +14 -0
  52. data/lib/webhookdb/fixtures/saved_views.rb +25 -0
  53. data/lib/webhookdb/fixtures/webhook_subscription_deliveries.rb +18 -0
  54. data/lib/webhookdb/http.rb +8 -2
  55. data/lib/webhookdb/icalendar.rb +3 -0
  56. data/lib/webhookdb/idempotency.rb +69 -22
  57. data/lib/webhookdb/increase.rb +69 -21
  58. data/lib/webhookdb/intercom.rb +10 -3
  59. data/lib/webhookdb/jobs/backfill.rb +3 -1
  60. data/lib/webhookdb/jobs/emailer.rb +0 -1
  61. data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +19 -0
  62. data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +1 -1
  63. data/lib/webhookdb/jobs/icalendar_sync.rb +1 -1
  64. data/lib/webhookdb/jobs/increase_event_handler.rb +20 -0
  65. data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -1
  66. data/lib/webhookdb/jobs/sync_target_run_sync.rb +3 -1
  67. data/lib/webhookdb/message/body.rb +6 -4
  68. data/lib/webhookdb/message/delivery.rb +2 -0
  69. data/lib/webhookdb/messages/error_icalendar_fetch.rb +1 -2
  70. data/lib/webhookdb/messages/error_signalwire_send_sms.rb +48 -0
  71. data/lib/webhookdb/oauth/fake_provider.rb +44 -0
  72. data/lib/webhookdb/oauth/front_provider.rb +1 -2
  73. data/lib/webhookdb/oauth/increase_provider.rb +80 -0
  74. data/lib/webhookdb/oauth/intercom_provider.rb +3 -11
  75. data/lib/webhookdb/oauth/session.rb +20 -0
  76. data/lib/webhookdb/oauth.rb +7 -21
  77. data/lib/webhookdb/organization/alerting.rb +2 -0
  78. data/lib/webhookdb/organization/database_migration.rb +3 -0
  79. data/lib/webhookdb/organization.rb +37 -6
  80. data/lib/webhookdb/organization_membership.rb +14 -7
  81. data/lib/webhookdb/postgres.rb +2 -0
  82. data/lib/webhookdb/replicator/base.rb +1 -0
  83. data/lib/webhookdb/replicator/docgen.rb +9 -1
  84. data/lib/webhookdb/replicator/fake.rb +2 -3
  85. data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +49 -14
  86. data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +97 -17
  87. data/lib/webhookdb/replicator/icalendar_event_v1.rb +104 -2
  88. data/lib/webhookdb/replicator/increase_account_number_v1.rb +6 -43
  89. data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +7 -24
  90. data/lib/webhookdb/replicator/increase_account_v1.rb +7 -31
  91. data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +5 -43
  92. data/lib/webhookdb/replicator/increase_app_v1.rb +78 -0
  93. data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +23 -29
  94. data/lib/webhookdb/replicator/increase_event_v1.rb +41 -0
  95. data/lib/webhookdb/replicator/increase_limit_v1.rb +9 -34
  96. data/lib/webhookdb/replicator/increase_transaction_v1.rb +5 -30
  97. data/lib/webhookdb/replicator/increase_v1_mixin.rb +58 -78
  98. data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +5 -24
  99. data/lib/webhookdb/replicator/intercom_contact_v1.rb +51 -4
  100. data/lib/webhookdb/replicator/intercom_conversation_v1.rb +42 -6
  101. data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +2 -13
  102. data/lib/webhookdb/replicator/intercom_v1_mixin.rb +20 -16
  103. data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +1 -1
  104. data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
  105. data/lib/webhookdb/replicator/transistor_episode_v1.rb +17 -0
  106. data/lib/webhookdb/replicator/url_recorder_v1.rb +137 -0
  107. data/lib/webhookdb/replicator/webhook_request.rb +4 -0
  108. data/lib/webhookdb/replicator.rb +8 -0
  109. data/lib/webhookdb/role.rb +5 -2
  110. data/lib/webhookdb/saved_query.rb +23 -0
  111. data/lib/webhookdb/saved_view.rb +73 -0
  112. data/lib/webhookdb/sentry.rb +2 -0
  113. data/lib/webhookdb/service/entities.rb +0 -4
  114. data/lib/webhookdb/service/helpers.rb +5 -0
  115. data/lib/webhookdb/service/middleware.rb +9 -0
  116. data/lib/webhookdb/service/types.rb +10 -8
  117. data/lib/webhookdb/service/validators.rb +1 -2
  118. data/lib/webhookdb/service/view_api.rb +1 -1
  119. data/lib/webhookdb/service_integration.rb +17 -15
  120. data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +8 -8
  121. data/lib/webhookdb/spec_helpers/whdb.rb +3 -2
  122. data/lib/webhookdb/subscription.rb +2 -0
  123. data/lib/webhookdb/sync_target.rb +10 -2
  124. data/lib/webhookdb/tasks/message.rb +3 -1
  125. data/lib/webhookdb/version.rb +1 -1
  126. data/lib/webhookdb/webhook_subscription/delivery.rb +2 -0
  127. data/lib/webhookdb/webhook_subscription.rb +2 -0
  128. metadata +57 -9
  129. data/lib/webhookdb/admin_api/customers.rb +0 -63
  130. data/lib/webhookdb/admin_api/message_deliveries.rb +0 -61
  131. data/lib/webhookdb/admin_api/roles.rb +0 -15
Binary file
@@ -0,0 +1,130 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
8
+ />
9
+ <meta name="theme-color" content="#000000" />
10
+ <link rel="icon" type="image/png" sizes="32x32" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAE4klEQVRYR61XCWwUVRj+phehFhFrFA0EsIVSrnCUUIWUgMhVhajBQtpEBbEgBhKQQzSEGO4oIAa25YhRK5eRQCAgQWuVWigFqYqiSFMIjchVgQLFXs/v7+yyM7uzMw/CSybtzvzX+4/vfc+A7lqrEhCNTBgYTJU0Ph34PORXv86/lfx2DAqFiMHXmGTU6Jg2PIU2qmTUYyaikEPZVp7yIqAgAW1DI1ZgmnHaTSdyAPkqnoYWUHk6d9ZSy3GokMJ/fLWS+ouQa9xysuEcQJ7qTKVtVOhzT47DlY5zM1mYYvwV+ik8gHzVk0L7+Tzu5jztYWB8EvBYPHDsEvAZTVfXu4Z7nl9HMBO/WqXsAZg7/97L+axuwLKngZiooKlzrPrwvcAfUv3I6zwzMdiaiWAAZs2LGYBr2vu2AUpfMp03KeA2d90yFjBoqZR7fGq39KDrKufXgYGeCAaQp5bQ+TteNf+gPzCLIdY1AiP3AIeZ/lXpQG4POqbn7uyck+5ZkAiXMwvzxJcZwFqOWjRO8FcLrwA+zwByugIVV4Hk7ab0sLbAgTHm/8/sAgoveFhRqCVW9MLrxmkzgDy1ns4nezmX7zNY/9WDzN2u+Ako+gdY2BcYwJatZTnafwFcqdOxBB/L8KaBTaoVgaaKATyoo9Y6BihnD3RsbZeWun/IgGYf1bHSLFODBjxhIF+N4w9/MvWUU4iH+0YDnSxBbDgBTDsE1Ht0oM1DEyYYWK98bIopeq5NqTgWrmI80M4PzDINSUz9GUesc7XskwwcoQh7W39NSgY2DDFHT9ZZdv2TWzmW+iYCkmUSAAcJj+jqdmWnlIwF2lhOhzU/szlLdS3Y5C5JAHJgxOmoJzHl32byHLa06w12fM8vmf6bpoVnOQ2NxIjCizoWUacdQAdiftHz9u6X2r/xHbDJcuAWPwcUczTn6U1DcwACG4+6xduCsHuQhvsTcAJLcGApnbx73K55IRtIYD77feV5LojiZa0mfIvI9zER0Lq2nAKyi8Jx/zJpSyKztbcSyDzgWYbmJlxHsaluomWE2TTL7qtrgc7s+tDjN5ZTUf2KmYFGjkQKZSpuuAaRbxCGs4iCFHVeMvP/0mi8pU03/8ndy6EdslLZnL9lBcdzYiHwiRsha0KWwYMogQfD37TlyPek/jWvAbHRQW/rSCkE9ULXYp4J84Wu+tfbJYRnIqTjEt4Yi3YmlPhYhijnMgjnqJpAemQJ71Q10HsHDx8L8qQTSb5hoz5gydQL+4Cd5yIE0ISNmGpMNgMQ5tuAXyKRzwIS8ewUu6FDzJnvd+A6cSCDs5/b3e78dgNPxgK2udPJKGQ1Bj2Cx7HYzlPLGMBcp3j7kAUdfpFoZSmDa2vx46cngVcPRkz/UhKS+fLVTsmAH/mut5PajFTya/KAKO+bBCpJVtJJTC4KxoYvQY5B4ZTMzIIrKc3uBHzEIBIj3BIEnErIC3OIjgFotvlXIEYiw5mUBiQ9aHkim2xiF2AsL2appOZxJChXiQtlQs0JTruriAFOnECcGxjuTssDQdz/i0k5OcfLeheTYCaEpr/HnzN1yKpjtYV8Amuo//7dXc2s1swRncNXgphavJGycjMuINNeKaPmNjEaPe1XF/Jah1EErKF8Q8wDW/JOQNf4/xk+R0mLfuCzh7di91PAb/Z/EtJ0nCBsdHkAAAAASUVORK5CYII=" />
11
+ <link rel="icon" type="image/png" sizes="16x16" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACHklEQVQ4T22TTUhUcRTFf0+DnNGgWkiKobORICiSoo+ViVJRJi2K2hY2k61y0SaoIGgjSETqTLkICqKNZWQgkW2i6MMkR8igKIKiMgoCxxGzf+f6VN5744X3de//nnfvued6RK3bbaCIJB4NOBJ6mn3U9Ujf10h5b4Ipftis08Uoo1OHWvVVXABsDsc/3a+So512b8pcPoAllzKgt50LiVVxuLwDymNwYRgGv4YgHzPJXgPxAdKuR1Cp4JG+RqheAeO/YV81VNyE3GwIJEPSS3lK3ij3awEUBcNPmmHslxr/ArcEVnEDJqZDALPKqPPIuCtyn4z23FQJ/bshtgw6RuD0yyVYcXQbwFuF1kXDler9wxHjDVZfh7zRV2jjBpCXf3kwVqIZDOyChiq4/R4OD0GiVLOcjCA4po2DvPpfBDBW7zbB/gR8+gPb++GbBvaiBervFxApgEgLzWvh3h6YEeOb+2BUUzDQ3FE4Kx46soEqHO+sgi6daFtwn9sE57fAyATU3fG961dC9iA8+KyRDoba6PIw6RZrjPPqO1YLvfX64wwcegg/xdAlCWrbGugZg7an8wC+KjVGs4xL65601zKNbfgA1K4KEzb1F7aqoqxamjNHWntxYkkpl5fARbXRUgNxAT7/DmdewbMfi6ARKZvf9iGuZYLjUVUGajEx92oPToWXKVitSdvTRjoa9ayZU5KndXYMiasMrd5o8Ph/oDCo9HTUhIAAAAAASUVORK5CYII=" />
12
+ <title>WebhookDB Admin</title>
13
+ <style>
14
+ body {
15
+ margin: 0;
16
+ padding: 0;
17
+ font-family: sans-serif;
18
+ }
19
+
20
+ .loader-container {
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ flex-direction: column;
25
+ position: absolute;
26
+ top: 0;
27
+ bottom: 0;
28
+ left: 0;
29
+ right: 0;
30
+ background-color: #2ba9ff;
31
+ }
32
+
33
+ /* CSS Spinner from https://projects.lukehaas.me/css-loaders/ */
34
+
35
+ .loader {
36
+ color: #ffffff;
37
+ font-size: 90px;
38
+ text-indent: -9999em;
39
+ overflow: hidden;
40
+ width: 1em;
41
+ height: 1em;
42
+ border-radius: 50%;
43
+ margin: 72px auto;
44
+ position: relative;
45
+ -webkit-transform: translateZ(0);
46
+ -ms-transform: translateZ(0);
47
+ transform: translateZ(0);
48
+ -webkit-animation: load6 1.7s infinite ease, round 1.7s infinite ease;
49
+ animation: load6 1.7s infinite ease, round 1.7s infinite ease;
50
+ }
51
+ @-webkit-keyframes load6 {
52
+ 0% {
53
+ box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
54
+ }
55
+ 5%,
56
+ 95% {
57
+ box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
58
+ }
59
+ 10%,
60
+ 59% {
61
+ box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em;
62
+ }
63
+ 20% {
64
+ box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em;
65
+ }
66
+ 38% {
67
+ box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em;
68
+ }
69
+ 100% {
70
+ box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
71
+ }
72
+ }
73
+ @keyframes load6 {
74
+ 0% {
75
+ box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
76
+ }
77
+ 5%,
78
+ 95% {
79
+ box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
80
+ }
81
+ 10%,
82
+ 59% {
83
+ box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em;
84
+ }
85
+ 20% {
86
+ box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em;
87
+ }
88
+ 38% {
89
+ box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em;
90
+ }
91
+ 100% {
92
+ box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
93
+ }
94
+ }
95
+ @-webkit-keyframes round {
96
+ 0% {
97
+ -webkit-transform: rotate(0deg);
98
+ transform: rotate(0deg);
99
+ }
100
+ 100% {
101
+ -webkit-transform: rotate(360deg);
102
+ transform: rotate(360deg);
103
+ }
104
+ }
105
+ @keyframes round {
106
+ 0% {
107
+ -webkit-transform: rotate(0deg);
108
+ transform: rotate(0deg);
109
+ }
110
+ 100% {
111
+ -webkit-transform: rotate(360deg);
112
+ transform: rotate(360deg);
113
+ }
114
+ }
115
+ </style>
116
+ <link rel="preconnect" href="https://fonts.gstatic.com" />
117
+ <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;500;600;700&display=swap" rel="stylesheet">
118
+ <script type="module" crossorigin src="/admin/assets/index-6aebf805.js"></script>
119
+ </head>
120
+
121
+ <body>
122
+ <noscript> You need to enable JavaScript to run this app. </noscript>
123
+ <div id="root">
124
+ <div class="loader-container">
125
+ <div class="loader">Loading...</div>
126
+ </div>
127
+ </div>
128
+ </body>
129
+
130
+ </html>
@@ -0,0 +1,15 @@
1
+ {
2
+ "short_name": "admin",
3
+ "name": "{{name}}",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ }
10
+ ],
11
+ "start_url": "./index.html",
12
+ "display": "standalone",
13
+ "theme_color": "#000000",
14
+ "background_color": "#ffffff"
15
+ }
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Thanks! - WebhookDB</title>
5
+ {% include 'web/partials/head' %}
6
+ </head>
7
+ <body>
8
+ <div class="layout">
9
+ <div class="flex column align-items-center content">
10
+ <div class="text-center mt-2">
11
+ {{ content }}
12
+ </div>
13
+ <div class="text-center mt-2 flex column align-items-middle w-100">
14
+ <hr class="w-100"/>
15
+ <span class="mt-2 text-small">Hosted at <a href="https://webhookdb.com">https://webhookdb.com</a></span>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </body>
20
+ </html>
@@ -0,0 +1,31 @@
1
+ {% expose subject %}WebhookDB: Signalwire SMS Error{% endexpose %}
2
+
3
+ {% partial 'greeting' %}
4
+
5
+ <p>
6
+ WebhookDB encountered an error trying to send an SMS via Signalwire using your credentials. We have the following information about the failure:
7
+ </p>
8
+ <ul>
9
+ <li>Service Integration Name: {{ service_name }}, ID: <code>{{ opaque_id }}</code></li>
10
+ <li>Request: <code>{{ request_method }} {{ request_url }}</code></li>
11
+ <li>Response Status: <code>{{ response_status }}</code></li>
12
+ <li>Body: <code>{{ response_body }}</code></li>
13
+ </ul>
14
+ <p>
15
+ Usually this indicates an API key has been revoked, or the parameters to your integration (like the 'from' number)
16
+ are incorrect, rather than being an error in WebhookDB itself.
17
+ </p>
18
+ <p>To fix this integration, head over to <a href="{{ app_url }}">{{ app_url }}</a>, log in, and run:</p>
19
+ <pre>
20
+ webhookdb integrations reset {{ opaque_id }}
21
+ </pre>
22
+ <p>
23
+ If you no longer wish to use this integration, it can be deleted using:
24
+ </p>
25
+ <pre>
26
+ webhookdb integrations delete {{ opaque_id }}
27
+ </pre>
28
+ <p>We'll continue to send daily emails when this happens, so please do fix this up when you get a chance.</p>
29
+ <p>Please file an issue at {{ oss_repo }} if you need any help.</p>
30
+
31
+ {% partial 'signoff' %}
@@ -1,6 +1,7 @@
1
1
  <html lang="en-US">
2
2
  <head>
3
- {% include 'web/styles' %}
3
+ <title>WebhookDB | Sync {{ app_name }} | Login</title>
4
+ {% include 'web/partials/head' %}
4
5
  </head>
5
6
  <body>
6
7
  <div class="layout">
@@ -11,12 +12,12 @@
11
12
  <form method="POST" action="{{ action_url }}">
12
13
  {% if view == "email" %}
13
14
  <p>
14
- Welcome to WebhookDB! In order to complete this integration, we need your email address so that
15
- we can associate your information with an organization.
15
+ Welcome to WebhookDB! In order to finish your sync with {{ app_name }},
16
+ you need to sign up or log in.
16
17
  </p>
17
18
  <p>Enter your email:</p>
18
19
  <div class="input-group">
19
- <input class="input-inline" type="text" id="email" name="email"/>
20
+ <input class="input-inline" type="text" id="email" name="email" autofocus/>
20
21
  <input class="input-inline-button" type="submit" value="Log in"/>
21
22
  </div>
22
23
  {% partial 'web/partials/form_error' %}
@@ -31,7 +32,7 @@
31
32
  </p>
32
33
  <p>Enter the token from your email:</p>
33
34
  <div class="input-group">
34
- <input class="input-inline" type="text" id="otp_token" name="otp_token"/>
35
+ <input class="input-inline" type="text" id="otp_token" name="otp_token" autofocus/>
35
36
  <input class="input-inline-button" type="submit" value="Log in"/>
36
37
  </div>
37
38
  {% partial 'web/partials/form_error' %}
@@ -1,6 +1,6 @@
1
1
  <html lang="en-US">
2
2
  <head>
3
- {% include 'web/styles' %}
3
+ {% include 'web/partials/head' %}
4
4
  </head>
5
5
  <body>
6
6
  <div class="layout">
@@ -0,0 +1,25 @@
1
+ <html lang="en-US">
2
+ <head>
3
+ <title>WebhookDB | Sync {{ app_name }} | Error</title>
4
+ {% include 'web/partials/head' %}
5
+ </head>
6
+ <body>
7
+ <div class="layout">
8
+ <div class="flex column align-items-center content">
9
+ {% partial 'web/partials/header' %}
10
+ <div class="flex column">
11
+ <p>
12
+ Sorry, something went wrong installing your sync to {{ app_name }}.
13
+ </p>
14
+ <p>
15
+ If your installation finished successfully, head to <a href="{{ terminal_url }}">{{ terminal_url }}</a>
16
+ and run <code>webhookdb auth login</code> and <code>webhookdb db connection</code> to get your connection string.
17
+ </p>
18
+ <p>Or head to <a href="{{ install_url }}">{{ install_url }}</a> to go back through setup.</p>
19
+ <p>If you need any help, you can email <a href="mailto:hello@webhookdb.com">hello@webhookdb.com</a>.</p>
20
+ </div>
21
+ {% partial 'web/partials/footer' %}
22
+ </div>
23
+ </div>
24
+ </body>
25
+ </html>
@@ -0,0 +1,40 @@
1
+ <html lang="en-US">
2
+ <head>
3
+ <title>WebhookDB | Sync {{ app_name }} | Choose Organization</title>
4
+ {% include 'web/partials/head' %}
5
+ </head>
6
+ <body>
7
+ <div class="layout">
8
+ <div class="flex column align-items-center content">
9
+ {% partial 'web/partials/header' %}
10
+
11
+ <div class="mt-2">
12
+ <form method="POST" action="{{ action_url }}">
13
+ <p>
14
+ Choose which WebhookDB organization your {{ app_name }} data will be replicated to.
15
+ You can also create a new organization (and database) to hold this data.
16
+ </p>
17
+ <div class="input-radio-group">
18
+ {% for org in organizations %}
19
+ <div class="input-radio-control">
20
+ <input type="radio" id="org-{{ org.key }}" name="existing_org_key" value="{{ org.key }}" checked="{{ org.checked }}" />
21
+ <label for="org-{{ org.key }}">{{ org.name }}</label>
22
+ </div>
23
+ {% endfor %}
24
+ <div class="input-radio-control">
25
+ <input type="radio" id="new_org" name="existing_org_key" value=""/>
26
+ <label for="new_org">Create an Organization:</label>
27
+ <input id="new_org_name" type="text" name="new_org_name" aria-labelledby="new_org" onfocus="document.querySelector('#new_org').checked = true">
28
+ </div>
29
+ </div>
30
+ <input class="button w-100" type="submit" value="Submit"/>
31
+ {% partial 'web/partials/form_error' %}
32
+ <input type="hidden" id="state" name="state" value="{{ oauth_state }}">
33
+ </form>
34
+
35
+ </div>
36
+ {% partial 'web/partials/footer' %}
37
+ </div>
38
+ </div>
39
+ </body>
40
+ </html>
@@ -1,6 +1,7 @@
1
1
  <html lang="en-US">
2
2
  <head>
3
- {% include 'web/styles' %}
3
+ <title>WebhookDB | Sync {{ app_name }} | Success!</title>
4
+ {% include 'web/partials/head' %}
4
5
  </head>
5
6
  <body>
6
7
  <div class="layout">
@@ -1,6 +1,7 @@
1
1
  <html lang="en-US">
2
2
  <head>
3
- {% include 'web/styles' %}
3
+ <title>WebhookDB | Sync {{ app_name }}</title>
4
+ {% include 'web/partials/head' %}
4
5
  </head>
5
6
  <body>
6
7
  <div class="layout">
@@ -0,0 +1,2 @@
1
+ <link rel="icon" href="/terminal/favicon.ico" type="image/x-icon" />
2
+ {% include 'web/styles' %}
@@ -48,6 +48,10 @@
48
48
  text-align: center;
49
49
  }
50
50
 
51
+ .text-small {
52
+ font-size: 80%;
53
+ }
54
+
51
55
  .btn {
52
56
  background-color: var(--color-primary);
53
57
  border: none;
@@ -97,6 +101,12 @@
97
101
  font-size: 16px;
98
102
  padding-left: var(--spacing);
99
103
  border-radius: var(--border-radius);
104
+ outline: none;
105
+ }
106
+
107
+ input[type=text]:focus {
108
+ border: solid 1px var(--color-primary);
109
+ outline: solid 2px var(--color-primary) !important;
100
110
  }
101
111
 
102
112
  .input-inline {
@@ -122,6 +132,20 @@
122
132
  border-radius: 0 var(--border-radius) var(--border-radius) 0 !important;
123
133
  }
124
134
 
135
+ .input-radio-group {
136
+ display: flex;
137
+ flex-direction: column;
138
+ gap: 8px;
139
+ margin-bottom: 16px;
140
+ }
141
+
142
+ .input-radio-control {
143
+ display: flex;
144
+ flex-direction: row;
145
+ align-items: center;
146
+ gap: 8px;
147
+ }
148
+
125
149
  .form-error {
126
150
  background-color: var(--color-warning);
127
151
  border: solid 1px var(--color-warning-dark);
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:saved_views) do
6
+ primary_key :id
7
+ timestamptz :created_at, null: false, default: Sequel.function(:now)
8
+ timestamptz :updated_at
9
+
10
+ foreign_key :organization_id, :organizations, null: false, on_delete: :cascade
11
+ index :organization_id
12
+
13
+ text :name, null: false
14
+ unique [:organization_id, :name]
15
+ text :sql, null: false
16
+
17
+ foreign_key :created_by_id, :customers, null: true, on_delete: :set_null
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:backfill_job_service_integration_locks) do
6
+ primary_key :id
7
+ foreign_key :service_integration_id, :service_integrations, null: false, on_delete: :cascade, unique: true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ searchable = [
6
+ :backfill_jobs,
7
+ :customers,
8
+ :message_bodies,
9
+ :message_deliveries,
10
+ :organization_memberships,
11
+ :organization_database_migrations,
12
+ :organizations,
13
+ :roles,
14
+ :saved_queries,
15
+ :saved_views,
16
+ :service_integrations,
17
+ :subscriptions,
18
+ :sync_targets,
19
+ :webhook_subscription_deliveries,
20
+ :webhook_subscriptions,
21
+ ]
22
+ searchable.each do |tbl|
23
+ alter_table(tbl) do
24
+ add_column :text_search, :tsvector
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ alter_table(:oauth_sessions) do
6
+ add_column :token_json, :jsonb
7
+ drop_column :authorization_code
8
+ add_constraint(
9
+ :no_token_json_if_used,
10
+ "NOT (used_at IS NOT NULL AND token_json IS NOT NULL)",
11
+ )
12
+ end
13
+ end
14
+
15
+ down do
16
+ alter_table(:oauth_sessions) do
17
+ drop_column :token_json
18
+ add_column :authorization_code, :text
19
+ end
20
+ end
21
+ end
@@ -41,13 +41,13 @@ RSpec.describe "auth", :integration do
41
41
  customer = Webhookdb::Fixtures.customer.admin.instance
42
42
  auth_customer(customer)
43
43
 
44
- resp = get("/admin/v1/auth")
44
+ resp = get("/admin_api/v1/auth")
45
45
  expect(resp).to party_status(200)
46
46
  expect(resp).to party_response(match(hash_including(name: customer.name)))
47
47
 
48
48
  customer.remove_role(Webhookdb::Role.admin_role)
49
49
 
50
- resp = get("/admin/v1/auth")
50
+ resp = get("/admin_api/v1/auth")
51
51
  expect(resp).to party_status(401)
52
52
  end
53
53
  end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sequel/text_searchable"
4
+
5
+ module Sequel::Plugins::TextSearchable
6
+ DEFAULT_OPTIONS = {
7
+ column: :text_search,
8
+ search_options: {
9
+ to_tsquery: :websearch,
10
+ language: "english",
11
+ rank: true,
12
+ },
13
+ terms: nil,
14
+ }.freeze
15
+
16
+ def self.apply(model, *)
17
+ raise "The :dirty plugin must be loaded first. Use `plugin :dirty`." if
18
+ !model.plugins.include?(Sequel::Plugins::Dirty) ||
19
+ model.plugins.index(Sequel::Plugins::Dirty) > model.plugins.index(self)
20
+ end
21
+
22
+ def self.configure(model, opts=DEFAULT_OPTIONS)
23
+ opts = DEFAULT_OPTIONS.merge(opts)
24
+ model.text_search_column = opts[:column]
25
+ model.text_search_options = opts[:search_options]
26
+ model.text_search_terms = opts[:terms]
27
+ SequelTextSearchable.searchable_models << model
28
+ end
29
+
30
+ module DatasetMethods
31
+ def text_search(q, opts={})
32
+ full_opts = self.model.text_search_options.merge(tsvector: true).merge(opts)
33
+ return self.full_text_search(self.model.text_search_column, q, **full_opts)
34
+ end
35
+ end
36
+
37
+ module ClassMethods
38
+ attr_accessor :text_search_column, :text_search_options, :text_search_terms
39
+
40
+ def text_search_language = self.text_search_options.fetch(:language)
41
+
42
+ def text_search_reindex_all
43
+ did = 0
44
+ self.dataset.paged_each do |m|
45
+ m.text_search_reindex
46
+ did += 1
47
+ end
48
+ return did
49
+ end
50
+
51
+ def text_search_reindex_model(model_pk)
52
+ m = self.with_pk!(model_pk)
53
+ m.text_search_reindex
54
+ return m
55
+ end
56
+
57
+ def text_search_columns_and_ranks
58
+ raise NotImplementedError, "#{self.name} must implement text_search_terms" if
59
+ self.text_search_terms.nil?
60
+ return self.text_search_terms.map do |t|
61
+ if t.is_a?(Array)
62
+ col, rank = t
63
+ elsif t.is_a?(Hash)
64
+ col, rank = t.to_a.first
65
+ else
66
+ col = t
67
+ rank = nil
68
+ end
69
+ [col, rank]
70
+ end
71
+ end
72
+ end
73
+
74
+ module InstanceMethods
75
+ def after_create
76
+ super
77
+ self._run_after_model_hook
78
+ end
79
+
80
+ def after_update
81
+ super
82
+ if self.class.text_search_terms.nil?
83
+ # If the instance implements a custom text_search_terms, we have to always call it,
84
+ # since we can't otherwise know if relevant values have changed.
85
+ self._run_after_model_hook
86
+ return
87
+ end
88
+ has_changes = self.class.text_search_columns_and_ranks.any? { |col, _rank| self.previous_changes.include?(col) }
89
+ self._run_after_model_hook if has_changes
90
+ end
91
+
92
+ def _run_after_model_hook
93
+ if SequelTextSearchable.index_mode == :async
94
+ # We must refetch the model to index since it happens on another thread.
95
+ SequelTextSearchable.threadpool.post do
96
+ self.model.text_search_reindex_model(self.pk)
97
+ end
98
+
99
+ elsif SequelTextSearchable.index_mode == :sync
100
+ self.text_search_reindex
101
+ end
102
+ end
103
+
104
+ # Return the values used for the tsvector value.
105
+ #
106
+ # In general this should include relevant text fields (like name and descriptions)
107
+ # on the receiver and related objects.
108
+ #
109
+ # Each value in the array can be one of the following:
110
+ #
111
+ # - nil: skipped
112
+ # - str like 'value': Used in `to_tsvector('value')`.
113
+ # - tuple[str, str] like ('value, 'B'): Used in `setweight(to_tsvector('value'), 'B')
114
+ # - has a text_search_values_for_related' method: All of these are included in the returned list.
115
+ # Useful for adding all of a parent relation's fields to related components,
116
+ # while the parent may need a more complex text_search_values.
117
+ # - has a 'text_search_values' method: All of these are included in the returned list.
118
+ def text_search_terms
119
+ raise NotImplementedError, "#{self.class.name} must implement text_search_terms" if
120
+ self.model.text_search_terms.nil?
121
+ return self.model.text_search_columns_and_ranks.map do |col, rank|
122
+ val = self.send(col)
123
+ rank ? [val, rank] : val
124
+ end
125
+ end
126
+
127
+ def text_search_reindex
128
+ got_terms = self.text_search_terms
129
+ return if got_terms.empty?
130
+ terms = got_terms.flat_map { |t| _text_search_term_to_col_and_rank(t) }
131
+ exprs = terms.filter_map do |(col, rank)|
132
+ col = Sequel.function(:coalesce, col, "")
133
+ expr = Sequel.function(:to_tsvector, self.model.text_search_language, col)
134
+ expr = Sequel.function(:setweight, expr, rank) if rank
135
+ expr
136
+ end
137
+ full_expr = Sequel.join(exprs)
138
+ self.this.update(self.model.text_search_column => full_expr)
139
+ end
140
+
141
+ private def _text_search_term_to_col_and_rank(t, norank: false)
142
+ return nil if t.nil?
143
+ if t.is_a?(Array)
144
+ c, r = t
145
+ r = nil if norank
146
+ return [[c, r]]
147
+ end
148
+ if t.is_a?(Hash)
149
+ c, r = t.to_a.first
150
+ r = nil if norank
151
+ return [[c, r]]
152
+ end
153
+ related_cols_ranks = if t.respond_to?(:text_search_terms_for_related)
154
+ t.text_search_terms_for_related
155
+ elsif t.respond_to?(:text_search_terms)
156
+ t.text_search_terms
157
+ else
158
+ return [[t, nil]]
159
+ end
160
+ return related_cols_ranks.flat_map do |relterm|
161
+ _text_search_term_to_col_and_rank(relterm, norank: true)
162
+ end
163
+ end
164
+ end
165
+ end