class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController # rubocop:disable Metrics/ClassLength include Api::V1::InboxesHelper before_action :fetch_inbox, except: [:index, :create] before_action :fetch_agent_bot, only: [:set_agent_bot] before_action :validate_limit, only: [:create] # we are already handling the authorization in fetch inbox before_action :check_authorization, except: [:show, :health, :setup_channel_provider] before_action :validate_whatsapp_cloud_channel, only: [:health] def index @inboxes = policy_scope(Current.account.inboxes.order_by_name.includes(:channel, { avatar_attachment: [:blob] })) end def show; end # Deprecated: This API will be removed in 2.7.0 def assignable_agents @assignable_agents = @inbox.assignable_agents end def campaigns @campaigns = @inbox.campaigns end def avatar @inbox.avatar.attachment.destroy! if @inbox.avatar.attached? head :ok end def create ActiveRecord::Base.transaction do channel = create_channel @inbox = Current.account.inboxes.build( { name: inbox_name(channel), channel: channel }.merge( permitted_params.except(:channel) ) ) @inbox.save! end end def update captain_unit_param_present = params.key?(:captain_unit_id) || params.key?('captain_unit_id') inbox_params = permitted_params.except(:channel, :csat_config, :captain_unit_id) captain_unit_id = permitted_params[:captain_unit_id] if captain_unit_param_present inbox_params[:csat_config] = format_csat_config(permitted_params[:csat_config]) if permitted_params[:csat_config].present? @inbox.update!(inbox_params) sync_captain_unit_link(captain_unit_id) if captain_unit_param_present update_inbox_working_hours update_channel if channel_update_required? end def agent_bot @agent_bot = @inbox.agent_bot end def set_agent_bot if @agent_bot agent_bot_inbox = @inbox.agent_bot_inbox || AgentBotInbox.new(inbox: @inbox) agent_bot_inbox.agent_bot = @agent_bot agent_bot_inbox.save! elsif @inbox.agent_bot_inbox.present? @inbox.agent_bot_inbox.destroy! end head :ok end def setup_channel_provider channel = @inbox.channel unless channel.respond_to?(:setup_channel_provider) render json: { error: 'Channel does not support setup' }, status: :unprocessable_entity and return end channel.setup_channel_provider head :ok end def disconnect_channel_provider channel = @inbox.channel unless channel.respond_to?(:disconnect_channel_provider) render json: { error: 'Channel does not support disconnect' }, status: :unprocessable_entity and return end channel.disconnect_channel_provider head :ok ensure channel.update_provider_connection!(connection: 'close') if channel.respond_to?(:update_provider_connection!) end def destroy ::DeleteObjectJob.perform_later(@inbox, Current.user, request.ip) if @inbox.present? render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') } end def sync_templates return render status: :unprocessable_entity, json: { error: 'Template sync is only available for WhatsApp channels' } unless whatsapp_channel? trigger_template_sync render status: :ok, json: { message: 'Template sync initiated successfully' } rescue StandardError => e render status: :internal_server_error, json: { error: e.message } end def health health_data = Whatsapp::HealthService.new(@inbox.channel).fetch_health_status render json: health_data rescue StandardError => e Rails.logger.error "[INBOX HEALTH] Error fetching health data: #{e.message}" render json: { error: e.message }, status: :unprocessable_entity end def on_whatsapp params.require(:phone_number) phone_number = params[:phone_number] channel = @inbox.channel unless channel.respond_to?(:on_whatsapp) render json: { error: 'Channel does not support whatsapp check' }, status: :unprocessable_entity and return end response = channel.on_whatsapp(phone_number) render json: response, status: :ok end private def fetch_inbox @inbox = Current.account.inboxes.find(params[:id]) authorize @inbox, :show? end def fetch_agent_bot @agent_bot = AgentBot.find(params[:agent_bot]) if params[:agent_bot] end def validate_whatsapp_cloud_channel return if @inbox.channel.is_a?(Channel::Whatsapp) && @inbox.channel.provider == 'whatsapp_cloud' render json: { error: 'Health data only available for WhatsApp Cloud API channels' }, status: :bad_request end def create_channel return unless allowed_channel_types.include?(permitted_params[:channel][:type]) account_channels_method.create!(permitted_params(channel_type_from_params::EDITABLE_ATTRS)[:channel].except(:type)) end def allowed_channel_types %w[web_widget api email line telegram whatsapp sms] end def update_inbox_working_hours @inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours] end def update_channel channel_attributes = get_channel_attributes(@inbox.channel_type) return if permitted_params(channel_attributes)[:channel].blank? validate_and_update_email_channel(channel_attributes) if @inbox.inbox_type == 'Email' reauthorize_and_update_channel(channel_attributes) update_channel_feature_flags end def channel_update_required? permitted_params(get_channel_attributes(@inbox.channel_type))[:channel].present? end def validate_and_update_email_channel(channel_attributes) validate_email_channel(channel_attributes) rescue StandardError => e render json: { message: e }, status: :unprocessable_entity and return end def reauthorize_and_update_channel(channel_attributes) @inbox.channel.reauthorized! if @inbox.channel.respond_to?(:reauthorized!) @inbox.channel.update!(permitted_params(channel_attributes)[:channel]) end def update_channel_feature_flags return unless @inbox.web_widget? return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags @inbox.channel.selected_feature_flags = permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel][:selected_feature_flags] @inbox.channel.save! end def format_csat_config(config) formatted = { 'display_type' => config['display_type'] || 'emoji', 'message' => config['message'] || '', :survey_rules => { 'operator' => config.dig('survey_rules', 'operator') || 'contains', 'values' => config.dig('survey_rules', 'values') || [] }, 'button_text' => config['button_text'] || 'Please rate us', 'language' => config['language'] || 'en' } format_template_config(config, formatted) formatted end def format_template_config(config, formatted) formatted['template'] = config['template'] if config['template'].present? end def inbox_attributes [:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled, :enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved, :lock_to_single_conversation, :portal_id, :sender_name_type, :business_name, :captain_unit_id, :typing_delay, :message_signature_enabled, :message_signature_default_name, :message_signature_day_name, :message_signature_night_even_name, :message_signature_night_odd_name, :message_signature_night_shift_start, :message_signature_night_shift_end, { csat_config: [:display_type, :message, :button_text, :language, { survey_rules: [:operator, { values: [] }], template: [:name, :template_id, :friendly_name, :content_sid, :approval_sid, :created_at, :linked_at, :language, :source, :status, { body_variables: {} }] }] }] end def permitted_params(channel_attributes = []) # We will remove this line after fixing https://linear.app/chatwoot/issue/CW-1567/null-value-passed-as-null-string-to-backend params.each { |k, v| params[k] = params[k] == 'null' ? nil : v } params.permit(*inbox_attributes, channel: [:type, *channel_attributes]) end def channel_type_from_params { 'web_widget' => Channel::WebWidget, 'api' => Channel::Api, 'email' => Channel::Email, 'line' => Channel::Line, 'telegram' => Channel::Telegram, 'whatsapp' => Channel::Whatsapp, 'sms' => Channel::Sms }[permitted_params[:channel][:type]] end def get_channel_attributes(channel_type) channel_type.constantize.const_defined?(:EDITABLE_ATTRS) ? channel_type.constantize::EDITABLE_ATTRS.presence : [] end def whatsapp_channel? @inbox.whatsapp? || (@inbox.twilio? && @inbox.channel.whatsapp?) end def trigger_template_sync if @inbox.whatsapp? Channels::Whatsapp::TemplatesSyncJob.perform_later(@inbox.channel) elsif @inbox.twilio? && @inbox.channel.whatsapp? Channels::Twilio::TemplatesSyncJob.perform_later(@inbox.channel) end end def sync_captain_unit_link(captain_unit_id) return unless defined?(Captain::Unit) account_units = Current.account.captain_units account_units.where(inbox_id: @inbox.id).update_all(inbox_id: nil) selected_unit_id = captain_unit_id.presence if selected_unit_id selected_unit = account_units.find(selected_unit_id) selected_unit.update!(inbox_id: @inbox.id) end return unless defined?(CaptainInbox) if selected_unit_id CaptainInbox.where(captain_unit_id: selected_unit_id) .where.not(inbox_id: @inbox.id) .update_all(captain_unit_id: nil) end @inbox.captain_inbox&.update!(captain_unit_id: selected_unit_id) end end Api::V1::Accounts::InboxesController.prepend_mod_with('Api::V1::Accounts::InboxesController')