beyond_canvas 0.22.0.pre → 0.23.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/icons/adblocker.svg +5 -0
  3. data/app/assets/images/icons/checkbox_checked.svg +1 -1
  4. data/app/assets/images/icons/checkbox_unchecked.svg +1 -1
  5. data/app/assets/images/icons/external_link.svg +1 -0
  6. data/app/assets/images/icons/toggle.svg +1 -0
  7. data/app/assets/images/icons/toggle_checked.svg +3 -0
  8. data/app/assets/images/icons/toggle_unchecked.svg +3 -0
  9. data/app/assets/javascripts/beyond_canvas/base.js +77 -31
  10. data/app/assets/stylesheets/beyond_canvas/base.scss +19 -18
  11. data/app/assets/stylesheets/beyond_canvas/components/_action_bar.scss +11 -6
  12. data/app/assets/stylesheets/beyond_canvas/components/_breadcrumbs.scss +5 -5
  13. data/app/assets/stylesheets/beyond_canvas/components/_buttons.scss +45 -95
  14. data/app/assets/stylesheets/beyond_canvas/components/_cards.scss +20 -12
  15. data/app/assets/stylesheets/beyond_canvas/components/_collapse.scss +6 -3
  16. data/app/assets/stylesheets/beyond_canvas/components/_flash.scss +11 -11
  17. data/app/assets/stylesheets/beyond_canvas/components/_forms.scss +2 -2
  18. data/app/assets/stylesheets/beyond_canvas/components/_inputs.scss +182 -40
  19. data/app/assets/stylesheets/beyond_canvas/components/_links.scss +51 -3
  20. data/app/assets/stylesheets/beyond_canvas/components/_main.scss +3 -3
  21. data/app/assets/stylesheets/beyond_canvas/components/_menu.scss +13 -0
  22. data/app/assets/stylesheets/beyond_canvas/components/_modals.scss +48 -18
  23. data/app/assets/stylesheets/beyond_canvas/components/_notices.scss +11 -11
  24. data/app/assets/stylesheets/beyond_canvas/components/_scrollbox.scss +5 -4
  25. data/app/assets/stylesheets/beyond_canvas/components/_select2.scss +13 -13
  26. data/app/assets/stylesheets/beyond_canvas/components/_statuses.scss +9 -8
  27. data/app/assets/stylesheets/beyond_canvas/components/_step_list.scss +67 -0
  28. data/app/assets/stylesheets/beyond_canvas/components/_tables.scss +10 -5
  29. data/app/assets/stylesheets/beyond_canvas/components/_titles.scss +1 -1
  30. data/app/assets/stylesheets/beyond_canvas/mailer.scss +2 -1
  31. data/app/assets/stylesheets/beyond_canvas/settings/_base_variables.scss +241 -0
  32. data/app/assets/stylesheets/beyond_canvas/settings/{_variables.scss → _constant_variables.scss} +30 -64
  33. data/app/assets/stylesheets/beyond_canvas/settings/_custom_variables.scss +233 -0
  34. data/app/assets/stylesheets/beyond_canvas/settings/_typography.scss +7 -3
  35. data/app/assets/stylesheets/beyond_canvas/utilities/_mixins.scss +30 -3
  36. data/app/controllers/beyond_canvas/authentications_controller.rb +18 -2
  37. data/app/controllers/beyond_canvas/system_controller.rb +3 -1
  38. data/app/controllers/beyond_canvas/webhooks_controller.rb +49 -0
  39. data/app/controllers/concerns/beyond_canvas/add_blocker_check.rb +17 -0
  40. data/app/controllers/concerns/beyond_canvas/custom_styles.rb +54 -0
  41. data/app/controllers/concerns/beyond_canvas/locale_management.rb +31 -8
  42. data/app/controllers/concerns/beyond_canvas/request_validation.rb +6 -0
  43. data/app/form_builders/beyond_canvas/form_builder.rb +62 -49
  44. data/app/helpers/beyond_canvas/application_helper.rb +27 -4
  45. data/app/helpers/beyond_canvas/form_tag_helper.rb +130 -0
  46. data/app/javascript/beyond_canvas/base.js +0 -1
  47. data/app/javascript/beyond_canvas/initializers/buttons.js +10 -2
  48. data/app/javascript/beyond_canvas/initializers/flash.js +32 -10
  49. data/app/javascript/beyond_canvas/initializers/inputs.js +9 -4
  50. data/app/javascript/beyond_canvas/initializers/modals.js +46 -10
  51. data/app/views/beyond_canvas/mailer/_header.html.erb +2 -2
  52. data/app/views/beyond_canvas/shared/_flash.html.erb +14 -10
  53. data/app/views/beyond_canvas/shared/_head.html.erb +4 -0
  54. data/app/views/beyond_canvas/shared/_locales.html.erb +1 -1
  55. data/app/views/layouts/beyond_canvas/application.html.erb +0 -1
  56. data/app/views/layouts/beyond_canvas/public.html.erb +1 -1
  57. data/config/initializers/beyond_canvas/constants.rb +13 -0
  58. data/config/initializers/beyond_canvas/session_store.rb +8 -0
  59. data/lib/beyond_canvas/configuration.rb +9 -1
  60. data/lib/beyond_canvas/engine.rb +7 -0
  61. data/lib/beyond_canvas/rails/routes.rb +27 -7
  62. data/lib/beyond_canvas/version.rb +1 -1
  63. data/lib/beyond_canvas/webhook_event_registration.rb +19 -0
  64. data/lib/beyond_canvas.rb +6 -4
  65. data/lib/generators/beyond_canvas/install/install_generator.rb +6 -0
  66. data/lib/generators/beyond_canvas/install/templates/beyond_canvas.rb.erb +38 -1
  67. data/lib/generators/beyond_canvas/model/model_generator.rb +3 -0
  68. data/lib/generators/beyond_canvas/webhook/templates/webhooks_controller.rb +22 -0
  69. data/lib/generators/beyond_canvas/webhook/webhook_generator.rb +15 -0
  70. data/lib/models/concerns/authentication.rb +10 -3
  71. data/lib/models/concerns/utils.rb +1 -1
  72. data/lib/models/concerns/webhook.rb +123 -0
  73. data/lib/models/shop.rb +1 -0
  74. metadata +68 -39
  75. data/app/assets/stylesheets/beyond_canvas/components/_comments.scss +0 -6
  76. data/app/views/beyond_canvas/shared/_modal.html.erb +0 -6
  77. data/config/routes.rb +0 -12
@@ -1,5 +1,4 @@
1
1
  import 'jquery';
2
-
3
2
  import './initializers/buttons';
4
3
  import './initializers/collapse';
5
4
  import './initializers/flash';
@@ -23,13 +23,15 @@ const BUTTON_SELECTORS = '[class^="button"]';
23
23
  });
24
24
  };
25
25
 
26
- $(document).on('confirm:complete', function () {
26
+ $(document).on('confirm:complete', function () {
27
27
  $.restoreActionElements();
28
28
  });
29
29
 
30
- $(document).on('click', BUTTON_SELECTORS, function () {
30
+ $(document).on('click', BUTTON_SELECTORS, function (e) {
31
31
  var button = $(this);
32
32
 
33
+ if(e.target.attributes.getNamedItem('target')?.value === '_blank') return;
34
+
33
35
  $.disableActionElements();
34
36
 
35
37
  if (!button.hasClass('button--no-spinner')) {
@@ -38,10 +40,15 @@ const BUTTON_SELECTORS = '[class^="button"]';
38
40
  });
39
41
 
40
42
  $(document).on('ready page:load turbolinks:load', onDOMReady);
43
+
44
+ $(document).on('beforeunload turbolinks:before-visit', function () {
45
+ $.restoreActionElements();
46
+ });
41
47
  })(jQuery);
42
48
 
43
49
  $.extend({
44
50
  restoreActionElements: function () {
51
+ setTimeout(function () {
45
52
  // Hide spinners
46
53
  $(BUTTON_SELECTORS).each(function (_, button) {
47
54
  setTimeout(function () {
@@ -56,6 +63,7 @@ $.extend({
56
63
  $('a, input[type="submit"], input[type="button"], input[type="reset"], button').each(function () {
57
64
  $(this).removeClass('actions--disabled');
58
65
  });
66
+ }, 100);
59
67
  },
60
68
  disableActionElements: function () {
61
69
  $('a, input[type="submit"], input[type="button"], input[type="reset"], button').each(function () {
@@ -10,17 +10,39 @@
10
10
  };
11
11
 
12
12
  $(document).on('click', '.flash__close', function () {
13
- closeAlert();
13
+ $.closeAlert();
14
14
  });
15
15
 
16
- $(document).on('ready page:load turbolinks:load', onDOMReady);
16
+ $(document).on('ready page:load turbolinks:load bc.flash.shown', onDOMReady);
17
17
  })(jQuery);
18
18
 
19
- function closeAlert() {
20
- $('.flash')
21
- .removeClass('flash--shown')
22
- .delay(700)
23
- .queue(function () {
24
- $(this).remove();
25
- });
26
- }
19
+ $.extend({
20
+ showFlash: function(status, message) {
21
+ const flash = `
22
+ <div class="flash">
23
+ <div class="flash__icon flash__icon--${status}">
24
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm6 16.538l-4.592-4.548 4.546-4.587-1.416-1.403-4.545 4.589-4.588-4.543-1.405 1.405 4.593 4.552-4.547 4.592 1.405 1.405 4.555-4.596 4.591 4.55 1.403-1.416z"></path></svg>
25
+ </div>
26
+ <div class="flash__message">
27
+ ${message}
28
+ </div>
29
+ <div class="flash__close">
30
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M24 20.188l-8.315-8.209 8.2-8.282-3.697-3.697-8.212 8.318-8.31-8.203-3.666 3.666 8.321 8.24-8.206 8.313 3.666 3.666 8.237-8.318 8.285 8.203z"></path></svg>
31
+ </div>
32
+ </div>`;
33
+
34
+ $(document).trigger('bc.flash.show');
35
+ $('#flash').html(flash);
36
+ $(document).trigger('bc.flash.shown');
37
+ },
38
+ closeAlert: function() {
39
+ $(document).trigger('bc.flash.hide');
40
+ $('.flash')
41
+ .removeClass('flash--shown')
42
+ .delay(700)
43
+ .queue(function () {
44
+ $(this).remove();
45
+ });
46
+ $(document).trigger('bc.flash.hidden');
47
+ }
48
+ });
@@ -1,15 +1,15 @@
1
1
  (function ($) {
2
2
  const onDOMReady = function () {
3
3
  $('input[type="file"]').each(function () {
4
- var $input = $(this),
4
+ const $input = $(this),
5
5
  $label = $(`.input__file__text.${$input.attr('id')}`),
6
6
  labelVal = $label.html();
7
7
 
8
8
  $input.on('change', function (e) {
9
- var fileName = '';
9
+ let fileName = '';
10
10
 
11
11
  if (this.files && this.files.length > 1)
12
- fileName = (this.getAttribute('data-multiple-caption') || '').replace(
12
+ fileName = (this.getAttribute('data-multiple-caption') || '{count} files selected').replace(
13
13
  '{count}',
14
14
  this.files.length
15
15
  );
@@ -33,5 +33,10 @@
33
33
  });
34
34
  };
35
35
 
36
- $(document).on('ready page:load turbolinks:load', onDOMReady);
36
+ $(document).on('ready page:load turbolinks:load', () => {
37
+ const observer = new MutationObserver(() => onDOMReady());
38
+
39
+ onDOMReady();
40
+ observer.observe(document.body, { childList: true, subtree: true });
41
+ });
37
42
  })(jQuery);
@@ -1,14 +1,50 @@
1
- $.extend({
2
- displayModal: function (content, options = {}) {
3
- $('#modal').find('#modal__content').html(content);
4
- $('#modal').css('display', 'flex');
1
+ (function ($) {
2
+ const onDOMReady = function () {
3
+ $('.modal').each(function () {
4
+ $(this).hide().css('visibility', 'visible');
5
+ });
6
+ };
7
+
8
+ $(document).on('click', '[data-toggle="modal"]', function (e) {
9
+ e.preventDefault();
10
+
11
+ const dataTarget = $(this).attr('data-target');
12
+
5
13
  $.restoreActionElements();
6
- $(document).trigger('modal:opened', options['extraEventParameters']);
14
+ $(dataTarget).showModal();
15
+ });
16
+
17
+ $(document).on('click', '[data-dismiss="modal"]', function (e) {
18
+ e.preventDefault();
19
+
20
+ const dataTarget = $(this).closest('.modal');
21
+
22
+ $.restoreActionElements();
23
+ $(dataTarget).hideModal();
24
+ });
25
+
26
+ $(document).on('ready page:load turbolinks:load', onDOMReady);
27
+ })(jQuery);
28
+
29
+ $.fn.extend({
30
+ showModal: function () {
31
+ const modal = $(this);
32
+
33
+ modal.trigger('bc.modal.show');
34
+
35
+ $.restoreActionElements();
36
+ modal.css('display', 'flex');
37
+
38
+ modal.trigger('bc.modal.shown');
7
39
  },
8
- closeModal: function () {
9
- $('#modal').find('#modal__content').empty();
10
- $('#modal').css('display', 'none');
40
+ hideModal: function () {
41
+ const modal = $(this);
42
+
43
+ modal.trigger('bc.modal.hide');
44
+
11
45
  $.restoreActionElements();
12
- $(document).trigger('modal:closed');
13
- }
46
+ modal.hide();
47
+
48
+ modal.trigger('bc.modal.hidden');
49
+ },
14
50
  });
@@ -1,8 +1,8 @@
1
1
  <!-- Email Header : BEGIN -->
2
2
  <tr>
3
3
  <td style="padding: 20px 0; text-align: center">
4
- <% if BeyondCanvas.configuration.site_logo.present? %>
5
- <%= image_tag BeyondCanvas.configuration.site_logo, width: "200", height: "50", alt: "alt_text", border: "0", style: "height: auto; font-family: sans-serif; font-size: 15px; line-height: 15px; color: #555555;" %>
4
+ <% if BeyondCanvas.configuration.email_logo.present? %>
5
+ <%= image_tag BeyondCanvas.configuration.email_logo, width: "200", height: "50", border: "0", style: "height: auto; font-family: sans-serif; font-size: 15px; line-height: 15px; color: #555555;" %>
6
6
  <% end %>
7
7
  </td>
8
8
  </tr>
@@ -2,21 +2,25 @@
2
2
 
3
3
  <% flash.each do |key, value| %>
4
4
 
5
- <div class="flash">
5
+ <% unless value.blank? %>
6
6
 
7
- <div class="flash__icon flash__icon--<%= key %>">
8
- <%= get_flash_icon(key) %>
9
- </div>
7
+ <div class="flash">
10
8
 
11
- <div class="flash__message">
12
- <%= value %>
13
- </div>
9
+ <div class="flash__icon flash__icon--<%= key %>">
10
+ <%= get_flash_icon(key) %>
11
+ </div>
12
+
13
+ <div class="flash__message">
14
+ <%= value %>
15
+ </div>
16
+
17
+ <div class="flash__close">
18
+ <%= inline_svg_tag 'icons/flash_close.svg' %>
19
+ </div>
14
20
 
15
- <div class="flash__close">
16
- <%= inline_svg_tag 'icons/flash_close.svg' %>
17
21
  </div>
18
22
 
19
- </div>
23
+ <% end %>
20
24
 
21
25
  <% end %>
22
26
 
@@ -16,6 +16,10 @@
16
16
  <% end %>
17
17
  <% end %>
18
18
 
19
+ <% if BeyondCanvas.configuration.cockpit_app %>
20
+ <%= stylesheet_link_tag cookies[:custom_styles_url] %>
21
+ <% end %>
22
+
19
23
  <% BeyondCanvas.configuration.javascripts.each do |path| %>
20
24
  <% if BeyondCanvas.configuration.skip_webpacker %>
21
25
  <%= javascript_include_tag path %>
@@ -1,5 +1,5 @@
1
1
  <% if show_locale_switch? && !is_cockpit_app? %>
2
- <%= form_for :system, url: beyond_canvas.update_locale_path, method: :put do |f| %>
2
+ <%= form_for :system, url: update_locale_path, method: :put do |f| %>
3
3
  <%= f.select :locale, I18n.available_locales.collect { |l| [translate_locale(l), l] },
4
4
  { selected: cookies[:locale] },
5
5
  class: 'select--locale',
@@ -11,7 +11,6 @@
11
11
 
12
12
  <%= render 'beyond_canvas/shared/flash' %>
13
13
  <%= render 'beyond_canvas/shared/locales' %>
14
- <%= render 'beyond_canvas/shared/modal' %>
15
14
  <%= render 'beyond_canvas/shared/menu' %>
16
15
  <%= render 'beyond_canvas/shared/action_bar' %>
17
16
 
@@ -3,7 +3,7 @@
3
3
  <html>
4
4
  <%= render 'beyond_canvas/shared/head' %>
5
5
 
6
- <body class="body--public">
6
+ <body class="beyond-canvas body--public">
7
7
 
8
8
  <main class="main <%= params[:controller].gsub(/[\/_]/, "-") %>--<%= params[:action] %>">
9
9
 
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ if BeyondCanvas.configuration.cockpit_app == true && !Rails.env.development?
4
+ COOKIES_ATTRIBUTES = {
5
+ expires: 1.year.from_now,
6
+ same_site: :none,
7
+ secure: true
8
+ }.freeze
9
+ else
10
+ COOKIES_ATTRIBUTES = {
11
+ expires: 1.year.from_now
12
+ }.freeze
13
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ if BeyondCanvas.configuration.cockpit_app == true && !Rails.env.development?
4
+ Rails.application.config.session_store :cookie_store, {
5
+ secure: true,
6
+ same_site: :none
7
+ }
8
+ end
@@ -3,26 +3,30 @@
3
3
  module BeyondCanvas
4
4
  class Configuration # :nodoc:
5
5
  attr_accessor :site_title, :site_logo, :favicon, :skip_webpacker, :encryption_key, :namespace, :cockpit_app,
6
- :open_app_url, :preinstalled, :debug_mode
6
+ :open_app_url, :preinstalled, :debug_mode, :webhook_site_url, :email_logo, :client_credentials
7
7
 
8
8
  include AssetRegistration
9
9
  include MenuItemRegistration
10
+ include WebhookEventRegistration
10
11
 
11
12
  def initialize
12
13
  @cockpit_app = false
13
14
  @debug_mode = false
15
+ @client_credentials = false
14
16
  @encryption_key = nil
15
17
  @favicon = nil
16
18
  @namespace = '/'
17
19
  @open_app_url = nil
18
20
  @preinstalled = false
19
21
  @site_logo = nil
22
+ @email_logo = nil
20
23
  @site_title = ::Rails.application.class.name.split('::').first.humanize
21
24
  @skip_webpacker = false
22
25
  end
23
26
 
24
27
  def setup!
25
28
  register_default_assets
29
+ register_default_webhook_events
26
30
  end
27
31
 
28
32
  private
@@ -31,5 +35,9 @@ module BeyondCanvas
31
35
  register_stylesheet 'beyond_canvas.css', media: 'screen'
32
36
  register_javascript 'beyond_canvas.js'
33
37
  end
38
+
39
+ def register_default_webhook_events
40
+ register_webhook_event('app.uninstalled')
41
+ end
34
42
  end
35
43
  end
@@ -21,6 +21,7 @@ module BeyondCanvas
21
21
  ActiveSupport.on_load :action_controller do
22
22
  include ::BeyondCanvas::LocaleManagement
23
23
  include ::BeyondCanvas::RequestValidation
24
+ include ::BeyondCanvas::CustomStyles
24
25
  include ::BeyondCanvas::StatusCodes
25
26
  include ::BeyondCanvas::AuthenticationsHelper
26
27
  include ::BeyondCanvas::DebugHelper
@@ -29,5 +30,11 @@ module BeyondCanvas
29
30
  ::ActionController::Base.helper BeyondCanvas::Engine.helpers
30
31
  end
31
32
  end
33
+
34
+ config.after_initialize do
35
+ ActiveSupport.on_load :action_controller do
36
+ include ::BeyondCanvas::AddBlockerCheck
37
+ end
38
+ end
32
39
  end
33
40
  end
@@ -3,18 +3,38 @@
3
3
  module ActionDispatch
4
4
  module Routing
5
5
  class Mapper # :nodoc:
6
- def beyond_canvas_routes(options = nil)
7
- mount BeyondCanvas::Engine => BeyondCanvas.configuration.namespace
6
+ def beyond_canvas_routes(*resources)
7
+ scope BeyondCanvas.configuration.namespace do
8
+ put 'locale', to: 'beyond_canvas/system#update_locale', as: :update_locale
9
+ end
10
+
11
+ resources = resources.extract_options!
12
+
13
+ beyond_canvas_for_controllers(resources[:controllers] || {})
14
+ end
8
15
 
9
- BeyondCanvas.use_rails_app_controller = options.present? && options[:custom_controller].present?
16
+ protected
10
17
 
11
- set_routes if BeyondCanvas.use_rails_app_controller
18
+ def beyond_canvas_for_controllers(controllers)
19
+ [:authentications, :webhooks].each do |method|
20
+ controllers.key?(method) ? send("set_#{method}_routes", controllers[method]) : send("set_#{method}_routes")
21
+ end
22
+ end
23
+
24
+ def set_webhooks_routes(controller = 'beyond_canvas/webhooks')
25
+ scope BeyondCanvas.configuration.namespace do
26
+ resources :shops, only: [] do
27
+ member do
28
+ post :beyond_webhook, controller: controller, action: :webhook
29
+ end
30
+ end
31
+ end
12
32
  end
13
33
 
14
- def set_routes
34
+ def set_authentications_routes(controller = 'beyond_canvas/authentications')
15
35
  scope BeyondCanvas.configuration.namespace do
16
- get 'callback', controller: :authentications, action: :new
17
- post 'callback', controller: :authentications, action: :install
36
+ get 'callback', controller: controller, action: :new
37
+ post 'callback', controller: controller, action: :install
18
38
  end
19
39
  end
20
40
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BeyondCanvas
4
- VERSION = '0.22.0.pre'
4
+ VERSION = '0.23.0.pre'
5
5
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module WebhookEventRegistration # :nodoc:
5
+ def register_webhook_event(event)
6
+ event.is_a?(Array) ? webhook_events.merge(event) : webhook_events.add(event)
7
+ end
8
+
9
+ def webhook_events
10
+ @webhook_events ||= Set.new
11
+ end
12
+
13
+ def clear_webhook_events!
14
+ webhook_events.clear
15
+ end
16
+
17
+ alias register_webhook_events register_webhook_event
18
+ end
19
+ end
data/lib/beyond_canvas.rb CHANGED
@@ -16,10 +16,11 @@ require 'beyond_api'
16
16
  require 'attr_encrypted'
17
17
 
18
18
  module BeyondCanvas # :nodoc:
19
- autoload :AssetRegistration, 'beyond_canvas/asset_registration'
20
- autoload :Configuration, 'beyond_canvas/configuration'
21
- autoload :MenuItemRegistration, 'beyond_canvas/menu_item_registration'
22
- autoload :ParameterSanitizer, 'beyond_canvas/parameter_sanitizer'
19
+ autoload :AssetRegistration, 'beyond_canvas/asset_registration'
20
+ autoload :Configuration, 'beyond_canvas/configuration'
21
+ autoload :MenuItemRegistration, 'beyond_canvas/menu_item_registration'
22
+ autoload :ParameterSanitizer, 'beyond_canvas/parameter_sanitizer'
23
+ autoload :WebhookEventRegistration, 'beyond_canvas/webhook_event_registration'
23
24
 
24
25
  module Models # :nodoc:
25
26
  autoload :Shop, 'models/shop'
@@ -27,6 +28,7 @@ module BeyondCanvas # :nodoc:
27
28
  module Concerns
28
29
  autoload :Authentication, 'models/concerns/authentication'
29
30
  autoload :Utils, 'models/concerns/utils'
31
+ autoload :Webhook, 'models/concerns/webhook'
30
32
  end
31
33
  end
32
34