diff --git a/app/controllers/webhook_endpoints_controller.rb b/app/controllers/webhook_endpoints_controller.rb new file mode 100644 index 0000000000..c9493f42bb --- /dev/null +++ b/app/controllers/webhook_endpoints_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class WebhookEndpointsController < ::BaseController + before_action :load_resource, only: :destroy + + def create + webhook_endpoint = spree_current_user.webhook_endpoints.new(webhook_endpoint_params) + + if webhook_endpoint.save + flash[:success] = t('.success') + else + flash[:error] = t('.error') + end + + redirect_to redirect_path + end + + def destroy + if @webhook_endpoint.destroy + flash[:success] = t('.success') + else + flash[:error] = t('.error') + end + + redirect_to redirect_path + end + + def load_resource + @webhook_endpoint = spree_current_user.webhook_endpoints.find(params[:id]) + end + + def webhook_endpoint_params + params.require(:webhook_endpoint).permit(:url) + end + + def redirect_path + if request.referer.blank? || request.referer.include?(spree.account_path) + developer_settings_path + else + request.referer + end + end + + def developer_settings_path + "#{spree.account_path}#/developer_settings" + end +end diff --git a/app/models/spree/user.rb b/app/models/spree/user.rb index 599815c055..879825e9ba 100644 --- a/app/models/spree/user.rb +++ b/app/models/spree/user.rb @@ -41,6 +41,7 @@ module Spree has_many :webhook_endpoints, dependent: :destroy accepts_nested_attributes_for :enterprise_roles, allow_destroy: true + accepts_nested_attributes_for :webhook_endpoints accepts_nested_attributes_for :bill_address accepts_nested_attributes_for :ship_address diff --git a/app/services/permitted_attributes/user.rb b/app/services/permitted_attributes/user.rb index 0b511bab21..4bb29bbcc4 100644 --- a/app/services/permitted_attributes/user.rb +++ b/app/services/permitted_attributes/user.rb @@ -15,7 +15,10 @@ module PermittedAttributes private def permitted_attributes - [:email, :password, :password_confirmation, :disabled] + [ + :email, :password, :password_confirmation, :disabled, + { webhook_endpoints_attributes: [:id, :url] }, + ] end end end diff --git a/app/views/spree/users/_developer_settings.html.haml b/app/views/spree/users/_developer_settings.html.haml index b98ed527d5..fe6f6c5d04 100644 --- a/app/views/spree/users/_developer_settings.html.haml +++ b/app/views/spree/users/_developer_settings.html.haml @@ -1,3 +1,4 @@ %script{ type: "text/ng-template", id: "account/developer_settings.html" } %h3= t('.title') = render partial: 'api_keys' + = render partial: 'webhook_endpoints' diff --git a/app/views/spree/users/_webhook_endpoints.html.haml b/app/views/spree/users/_webhook_endpoints.html.haml new file mode 100644 index 0000000000..8bd60e2f8b --- /dev/null +++ b/app/views/spree/users/_webhook_endpoints.html.haml @@ -0,0 +1,33 @@ +%section{ id: "webhook_endpoints" } + %hr + %h3= t('.title') + %p= t('.description') + + %table{width: "100%"} + %thead + %tr + %th= t('.event_type.header') + %th= t('.url.header') + %th.actions + %tbody + -# Existing endpoints + - @user.webhook_endpoints.each do |webhook_endpoint| + %tr + %td= t('.event_types.order_cycle_opened') # For now, we only support one type. + %td= webhook_endpoint.url + %td.actions + - if webhook_endpoint.persisted? + = button_to account_webhook_endpoint_path(webhook_endpoint), method: :delete, + class: "tiny alert no-margin", + data: { confirm: I18n.t(:are_you_sure)} do + = I18n.t(:delete) + + -# Create new + - if @user.webhook_endpoints.empty? # Only one allowed for now. + %tr + %td= t('.event_types.order_cycle_opened') # For now, we only support one type. + %td + = form_for(@user.webhook_endpoints.build, url: account_webhook_endpoints_path, id: 'new_webhook_endpoint') do |f| + = f.url_field :url, placeholder: t('.url.create_placeholder'), required: true, size: 64 + %td.actions + = button_tag t(:create), class: 'button primary tiny no-margin', form: 'new_webhook_endpoint' diff --git a/config/locales/en.yml b/config/locales/en.yml index 761411574e..81e2ace90d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3562,6 +3562,14 @@ See the %{link} to find out more about %{sitename}'s features and to start using previous: "Previous" last: "Last" + webhook_endpoints: + create: + success: Webhook endpoint successfully created + error: Webhook endpoint failed to create + destroy: + success: Webhook endpoint successfully deleted + error: Webhook endpoint failed to delete + spree: order_updated: "Order Updated" add_country: "Add country" @@ -4357,6 +4365,16 @@ See the %{link} to find out more about %{sitename}'s features and to start using api_keys: regenerate_key: "Regenerate Key" title: API key + webhook_endpoints: + title: Webhook Endpoints + description: Events in the system may trigger webhooks to external systems. + event_types: + order_cycle_opened: Order Cycle Opened + event_type: + header: Event type + url: + header: Endpoint URL + create_placeholder: Enter the URL of the remote webhook endpoint developer_settings: title: Developer Settings form: diff --git a/config/routes/spree.rb b/config/routes/spree.rb index c658cbdc21..9844b800e5 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -32,7 +32,9 @@ Spree::Core::Engine.routes.draw do put '/password/change' => 'user_passwords#update', :as => :update_password end - resource :account, :controller => 'users' + resource :account, :controller => 'users' do + resources :webhook_endpoints, only: [:create, :destroy], controller: '/webhook_endpoints' + end match '/admin/orders/bulk_management' => 'admin/orders#bulk_management', :as => "admin_bulk_order_management", via: :get match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get diff --git a/spec/controllers/webhook_endpoints_controller_spec.rb b/spec/controllers/webhook_endpoints_controller_spec.rb new file mode 100644 index 0000000000..c36720edb8 --- /dev/null +++ b/spec/controllers/webhook_endpoints_controller_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: false + +require 'spec_helper' +require 'open_food_network/order_cycle_permissions' + +describe WebhookEndpointsController, type: :controller do + let(:user) { create(:admin_user) } + + before { allow(controller).to receive(:spree_current_user) { user } } + + describe "#create" do + it "creates a webhook_endpoint" do + expect { + spree_post :create, { url: "https://url" } + }.to change { + user.webhook_endpoints.count + }.by(1) + + expect(flash[:success]).to be_present + expect(flash[:error]).to be_blank + expect(user.webhook_endpoints.first.url).to eq "https://url" + end + + it "shows error if parameters not specified" do + expect { + spree_post :create, { url: "" } + }.to_not change { + user.webhook_endpoints.count + } + + expect(flash[:success]).to be_blank + expect(flash[:error]).to be_present + end + + it "redirects back to referrer" do + spree_post :create, { url: "https://url" } + + expect(response).to redirect_to "/account#/developer_settings" + end + end + + describe "#destroy" do + let!(:webhook_endpoint) { user.webhook_endpoints.create(url: "https://url") } + + it "destroys a webhook_endpoint" do + webhook_endpoint2 = user.webhook_endpoints.create!(url: "https://url2") + + expect { + spree_delete :destroy, { id: webhook_endpoint.id } + }.to change { + user.webhook_endpoints.count + }.by(-1) + + expect(flash[:success]).to be_present + expect(flash[:error]).to be_blank + + expect{ webhook_endpoint.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect(webhook_endpoint2.reload).to be_present + end + + it "redirects back to developer settings tab" do + spree_delete :destroy, id: webhook_endpoint.id + + expect(response).to redirect_to "/account#/developer_settings" + end + end +end diff --git a/spec/system/consumer/account/developer_settings_spec.rb b/spec/system/consumer/account/developer_settings_spec.rb index 4385a4aff4..40bf4a59d6 100644 --- a/spec/system/consumer/account/developer_settings_spec.rb +++ b/spec/system/consumer/account/developer_settings_spec.rb @@ -35,6 +35,22 @@ describe "Developer Settings" do expect(page).to have_content "Key generated" expect(page).to have_input "api_key", with: user.reload.spree_api_key end + + describe "Webhook Endpoints" do + it "creates a new webhook endpoint and deletes it" do + within "#webhook_endpoints" do + fill_in "webhook_endpoint_url", with: "https://url" + + click_button I18n.t(:create) + expect(page.document).to have_content I18n.t('webhook_endpoints.create.success') + expect(page).to have_content "https://url" + + click_button I18n.t(:delete) + expect(page.document).to have_content I18n.t('webhook_endpoints.destroy.success') + expect(page).to_not have_content "https://url" + end + end + end end end