webhookdb 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) 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/040_saved_query_fix_unique.rb +17 -0
  17. data/db/migrations/041_views.rb +20 -0
  18. data/db/migrations/042_sint_lock.rb +10 -0
  19. data/db/migrations/043_text_search.rb +28 -0
  20. data/db/migrations/044_oauth_session_token_cache.rb +21 -0
  21. data/integration/auth_spec.rb +2 -2
  22. data/lib/sequel/plugins/text_searchable.rb +165 -0
  23. data/lib/sequel/text_searchable.rb +42 -0
  24. data/lib/webhookdb/admin_api/auth.rb +24 -3
  25. data/lib/webhookdb/admin_api/data_provider.rb +196 -0
  26. data/lib/webhookdb/admin_api/entities.rb +143 -28
  27. data/lib/webhookdb/admin_api.rb +0 -2
  28. data/lib/webhookdb/api/auth.rb +5 -6
  29. data/lib/webhookdb/api/db.rb +31 -6
  30. data/lib/webhookdb/api/entities.rb +7 -1
  31. data/lib/webhookdb/api/helpers.rb +6 -25
  32. data/lib/webhookdb/api/install.rb +204 -79
  33. data/lib/webhookdb/api/organizations.rb +14 -12
  34. data/lib/webhookdb/api/saved_queries.rb +10 -3
  35. data/lib/webhookdb/api/saved_views.rb +99 -0
  36. data/lib/webhookdb/api/service_integrations.rb +15 -9
  37. data/lib/webhookdb/api/subscriptions.rb +3 -1
  38. data/lib/webhookdb/api/sync_targets.rb +9 -7
  39. data/lib/webhookdb/api/system.rb +1 -0
  40. data/lib/webhookdb/api/webhook_subscriptions.rb +3 -1
  41. data/lib/webhookdb/apps.rb +30 -7
  42. data/lib/webhookdb/async/audit_logger.rb +2 -0
  43. data/lib/webhookdb/async.rb +5 -0
  44. data/lib/webhookdb/backfill_job/service_integration_lock.rb +22 -0
  45. data/lib/webhookdb/backfill_job.rb +9 -0
  46. data/lib/webhookdb/customer.rb +5 -0
  47. data/lib/webhookdb/database_document.rb +1 -1
  48. data/lib/webhookdb/db_adapter/default_sql.rb +1 -1
  49. data/lib/webhookdb/db_adapter.rb +20 -4
  50. data/lib/webhookdb/fixtures/message_bodies.rb +34 -0
  51. data/lib/webhookdb/fixtures/organizations.rb +5 -0
  52. data/lib/webhookdb/fixtures/roles.rb +14 -0
  53. data/lib/webhookdb/fixtures/saved_views.rb +25 -0
  54. data/lib/webhookdb/fixtures/webhook_subscription_deliveries.rb +18 -0
  55. data/lib/webhookdb/http.rb +8 -2
  56. data/lib/webhookdb/icalendar.rb +3 -0
  57. data/lib/webhookdb/idempotency.rb +69 -22
  58. data/lib/webhookdb/increase.rb +69 -21
  59. data/lib/webhookdb/intercom.rb +10 -3
  60. data/lib/webhookdb/jobs/backfill.rb +3 -1
  61. data/lib/webhookdb/jobs/emailer.rb +0 -1
  62. data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +19 -0
  63. data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +1 -1
  64. data/lib/webhookdb/jobs/icalendar_sync.rb +1 -1
  65. data/lib/webhookdb/jobs/increase_event_handler.rb +20 -0
  66. data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -1
  67. data/lib/webhookdb/jobs/sync_target_run_sync.rb +3 -1
  68. data/lib/webhookdb/message/body.rb +6 -4
  69. data/lib/webhookdb/message/delivery.rb +2 -0
  70. data/lib/webhookdb/messages/error_icalendar_fetch.rb +1 -2
  71. data/lib/webhookdb/messages/error_signalwire_send_sms.rb +48 -0
  72. data/lib/webhookdb/oauth/fake_provider.rb +44 -0
  73. data/lib/webhookdb/oauth/front_provider.rb +1 -2
  74. data/lib/webhookdb/oauth/increase_provider.rb +80 -0
  75. data/lib/webhookdb/oauth/intercom_provider.rb +3 -11
  76. data/lib/webhookdb/oauth/session.rb +20 -0
  77. data/lib/webhookdb/oauth.rb +7 -21
  78. data/lib/webhookdb/organization/alerting.rb +2 -0
  79. data/lib/webhookdb/organization/database_migration.rb +3 -0
  80. data/lib/webhookdb/organization.rb +37 -6
  81. data/lib/webhookdb/organization_membership.rb +14 -7
  82. data/lib/webhookdb/postgres.rb +2 -0
  83. data/lib/webhookdb/replicator/base.rb +1 -0
  84. data/lib/webhookdb/replicator/docgen.rb +9 -1
  85. data/lib/webhookdb/replicator/fake.rb +2 -3
  86. data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +49 -14
  87. data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +97 -17
  88. data/lib/webhookdb/replicator/icalendar_event_v1.rb +104 -2
  89. data/lib/webhookdb/replicator/increase_account_number_v1.rb +6 -43
  90. data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +7 -24
  91. data/lib/webhookdb/replicator/increase_account_v1.rb +7 -31
  92. data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +5 -43
  93. data/lib/webhookdb/replicator/increase_app_v1.rb +78 -0
  94. data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +23 -29
  95. data/lib/webhookdb/replicator/increase_event_v1.rb +41 -0
  96. data/lib/webhookdb/replicator/increase_limit_v1.rb +9 -34
  97. data/lib/webhookdb/replicator/increase_transaction_v1.rb +5 -30
  98. data/lib/webhookdb/replicator/increase_v1_mixin.rb +58 -78
  99. data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +5 -24
  100. data/lib/webhookdb/replicator/intercom_contact_v1.rb +51 -4
  101. data/lib/webhookdb/replicator/intercom_conversation_v1.rb +42 -6
  102. data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +2 -13
  103. data/lib/webhookdb/replicator/intercom_v1_mixin.rb +20 -16
  104. data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +1 -1
  105. data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
  106. data/lib/webhookdb/replicator/transistor_episode_v1.rb +17 -0
  107. data/lib/webhookdb/replicator/url_recorder_v1.rb +137 -0
  108. data/lib/webhookdb/replicator/webhook_request.rb +4 -0
  109. data/lib/webhookdb/replicator.rb +8 -0
  110. data/lib/webhookdb/role.rb +5 -2
  111. data/lib/webhookdb/saved_query.rb +23 -0
  112. data/lib/webhookdb/saved_view.rb +73 -0
  113. data/lib/webhookdb/sentry.rb +2 -0
  114. data/lib/webhookdb/service/entities.rb +0 -4
  115. data/lib/webhookdb/service/helpers.rb +5 -0
  116. data/lib/webhookdb/service/middleware.rb +17 -0
  117. data/lib/webhookdb/service/types.rb +10 -8
  118. data/lib/webhookdb/service/validators.rb +1 -2
  119. data/lib/webhookdb/service/view_api.rb +1 -1
  120. data/lib/webhookdb/service_integration.rb +17 -15
  121. data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +8 -8
  122. data/lib/webhookdb/spec_helpers/whdb.rb +3 -2
  123. data/lib/webhookdb/subscription.rb +2 -0
  124. data/lib/webhookdb/sync_target.rb +10 -2
  125. data/lib/webhookdb/tasks/message.rb +3 -1
  126. data/lib/webhookdb/version.rb +1 -1
  127. data/lib/webhookdb/webhook_subscription/delivery.rb +2 -0
  128. data/lib/webhookdb/webhook_subscription.rb +2 -0
  129. metadata +58 -9
  130. data/lib/webhookdb/admin_api/customers.rb +0 -63
  131. data/lib/webhookdb/admin_api/message_deliveries.rb +0 -61
  132. 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,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ alter_table(:saved_queries) do
6
+ drop_constraint(:saved_queries_organization_id_key)
7
+ add_index :organization_id
8
+ end
9
+ end
10
+
11
+ down do
12
+ alter_table(:saved_queries) do
13
+ drop_index :organization_id
14
+ add_unique_constraint(:organization_id)
15
+ end
16
+ end
17
+ end
@@ -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