From a0886d37bc19e4556c4f4c6d48db45e985fa5bff Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Wed, 21 Jul 2021 01:26:32 +0530 Subject: [PATCH] chore: Expose widget config via an API (#2645) Expose widget config via an API so that the UI could be detached from the rails Application. --- .../api/v1/widget/base_controller.rb | 4 ++ .../api/v1/widget/campaigns_controller.rb | 6 -- .../api/v1/widget/configs_controller.rb | 41 ++++++++++++ .../api/v1/widget/inbox_members_controller.rb | 6 -- app/controllers/widgets_controller.rb | 1 + .../v1/widget/configs/create.json.jbuilder | 28 ++++++++ config/routes.rb | 1 + .../api/v1/widget/configs_controller_spec.rb | 67 +++++++++++++++++++ 8 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 app/controllers/api/v1/widget/configs_controller.rb create mode 100644 app/views/api/v1/widget/configs/create.json.jbuilder create mode 100644 spec/controllers/api/v1/widget/configs_controller_spec.rb diff --git a/app/controllers/api/v1/widget/base_controller.rb b/app/controllers/api/v1/widget/base_controller.rb index 4c7dd4220..38c880526 100644 --- a/app/controllers/api/v1/widget/base_controller.rb +++ b/app/controllers/api/v1/widget/base_controller.rb @@ -94,6 +94,10 @@ class Api::V1::Widget::BaseController < ApplicationController { timestamp: permitted_params[:message][:timestamp] } end + def permitted_params + params.permit(:website_token) + end + def message_params { account_id: conversation.account_id, diff --git a/app/controllers/api/v1/widget/campaigns_controller.rb b/app/controllers/api/v1/widget/campaigns_controller.rb index fc26b18c8..cb9b96a38 100644 --- a/app/controllers/api/v1/widget/campaigns_controller.rb +++ b/app/controllers/api/v1/widget/campaigns_controller.rb @@ -4,10 +4,4 @@ class Api::V1::Widget::CampaignsController < Api::V1::Widget::BaseController def index @campaigns = @web_widget.inbox.campaigns.where(enabled: true) end - - private - - def permitted_params - params.permit(:website_token) - end end diff --git a/app/controllers/api/v1/widget/configs_controller.rb b/app/controllers/api/v1/widget/configs_controller.rb new file mode 100644 index 000000000..ac88c595a --- /dev/null +++ b/app/controllers/api/v1/widget/configs_controller.rb @@ -0,0 +1,41 @@ +class Api::V1::Widget::ConfigsController < Api::V1::Widget::BaseController + before_action :set_global_config + + def create + build_contact + set_token + end + + private + + def set_global_config + @global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'WIDGET_BRAND_URL') + end + + def set_contact + @contact_inbox = @web_widget.inbox.contact_inboxes.find_by( + source_id: auth_token_params[:source_id] + ) + @contact = @contact_inbox&.contact + end + + def build_contact + return if @contact.present? + + @contact_inbox = @web_widget.create_contact_inbox(additional_attributes) + @contact = @contact_inbox.contact + end + + def set_token + payload = { source_id: @contact_inbox.source_id, inbox_id: @web_widget.inbox.id } + @token = ::Widget::TokenService.new(payload: payload).generate_token + end + + def additional_attributes + if @web_widget.inbox.account.feature_enabled?('ip_lookup') + { created_at_ip: request.remote_ip } + else + {} + end + end +end diff --git a/app/controllers/api/v1/widget/inbox_members_controller.rb b/app/controllers/api/v1/widget/inbox_members_controller.rb index da7d6256b..c4bc377ea 100644 --- a/app/controllers/api/v1/widget/inbox_members_controller.rb +++ b/app/controllers/api/v1/widget/inbox_members_controller.rb @@ -4,10 +4,4 @@ class Api::V1::Widget::InboxMembersController < Api::V1::Widget::BaseController def index @inbox_members = @web_widget.inbox.inbox_members.includes(:user) end - - private - - def permitted_params - params.permit(:website_token) - end end diff --git a/app/controllers/widgets_controller.rb b/app/controllers/widgets_controller.rb index 44a23568e..f19a24888 100644 --- a/app/controllers/widgets_controller.rb +++ b/app/controllers/widgets_controller.rb @@ -1,3 +1,4 @@ +# TODO : Delete this and associated spec once 'api/widget/config' end point is merged class WidgetsController < ActionController::Base before_action :set_global_config before_action :set_web_widget diff --git a/app/views/api/v1/widget/configs/create.json.jbuilder b/app/views/api/v1/widget/configs/create.json.jbuilder new file mode 100644 index 000000000..8a7696a87 --- /dev/null +++ b/app/views/api/v1/widget/configs/create.json.jbuilder @@ -0,0 +1,28 @@ +json.chatwoot_website_channel do + json.avatar_url @web_widget.inbox.avatar_url + json.has_a_connected_agent_bot @web_widget.inbox.agent_bot&.name + json.locale @web_widget.account.locale + json.website_name @web_widget.inbox.name + json.website_token @web_widget.website_token + json.welcome_tagline @web_widget.welcome_tagline + json.welcome_title @web_widget.welcome_title + json.widget_color @web_widget.widget_color + json.enabled_features @web_widget.selected_feature_flags + json.enabled_languages available_locales_with_name + json.reply_time @web_widget.reply_time + json.pre_chat_form_enabled @web_widget.pre_chat_form_enabled + json.pre_chat_form_options @web_widget.pre_chat_form_options + json.working_hours_enabled @web_widget.inbox.working_hours_enabled + json.csat_survey_enabled @web_widget.inbox.csat_survey_enabled + json.working_hours @web_widget.inbox.working_hours + json.out_of_office_message @web_widget.inbox.out_of_office_message + json.utc_off_set ActiveSupport::TimeZone[@web_widget.inbox.timezone].formatted_offset +end +json.chatwoot_widget_defaults do + json.use_inbox_avatar_for_bot ActiveModel::Type::Boolean.new.cast(ENV.fetch('USE_INBOX_AVATAR_FOR_BOT', false)) +end +json.contact do + json.pubsub_token @contact.pubsub_token +end +json.auth_token @token +json.global_config @global_config diff --git a/config/routes.rb b/config/routes.rb index be7d26eb2..6d2ed531b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -152,6 +152,7 @@ Rails.application.routes.draw do resource :notification_subscriptions, only: [:create] namespace :widget do + resource :config, only: [:create] resources :campaigns, only: [:index] resources :events, only: [:create] resources :messages, only: [:index, :create, :update] diff --git a/spec/controllers/api/v1/widget/configs_controller_spec.rb b/spec/controllers/api/v1/widget/configs_controller_spec.rb new file mode 100644 index 000000000..b6bc0fba3 --- /dev/null +++ b/spec/controllers/api/v1/widget/configs_controller_spec.rb @@ -0,0 +1,67 @@ +require 'rails_helper' + +RSpec.describe '/api/v1/widget/config', type: :request do + let(:account) { create(:account) } + let(:web_widget) { create(:channel_widget, account: account) } + let!(:contact) { create(:contact, account: account) } + let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: web_widget.inbox) } + let(:payload) { { source_id: contact_inbox.source_id, inbox_id: web_widget.inbox.id } } + let(:token) { ::Widget::TokenService.new(payload: payload).generate_token } + + describe 'POST /api/v1/widget/config' do + let(:params) { { website_token: web_widget.website_token } } + let(:response_keys) { %w[chatwoot_website_channel chatwoot_widget_defaults contact auth_token global_config] } + + context 'with invalid website token' do + it 'returns not found' do + post '/api/v1/widget/config', params: { website_token: '' } + expect(response).to have_http_status(:not_found) + end + end + + context 'with correct website token and missing X-Auth-Token' do + it 'returns widget config along with a new contact' do + expect do + post '/api/v1/widget/config', + params: params, + as: :json + end.to change(Contact, :count).by(1) + + expect(response).to have_http_status(:success) + response_data = JSON.parse(response.body) + expect(response_data.keys).to include(*response_keys) + end + end + + context 'with correct website token and valid X-Auth-Token' do + it 'returns widget config along with the same contact' do + expect do + post '/api/v1/widget/config', + params: params, + headers: { 'X-Auth-Token' => token }, + as: :json + end.to change(Contact, :count).by(0) + + expect(response).to have_http_status(:success) + response_data = JSON.parse(response.body) + expect(response_data.keys).to include(*response_keys) + expect(response_data['contact']['pubsub_token']).to eq(contact.pubsub_token) + end + end + + context 'with correct website token and invalid X-Auth-Token' do + it 'returns widget config and new contact with error message' do + expect do + post '/api/v1/widget/config', + params: params, + headers: { 'X-Auth-Token' => 'invalid token' }, + as: :json + end.to change(Contact, :count).by(1) + + expect(response).to have_http_status(:success) + response_data = JSON.parse(response.body) + expect(response_data.keys).to include(*response_keys) + end + end + end +end