SumOfUs/Champaign

View on GitHub
app/services/call_creator.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

class CallCreator
  include Rails.application.routes.url_helpers
  attr_accessor :call, :page

  def initialize(params, extra_params = {})
    @params = params.clone
    @errors = {}
    @extra_params = extra_params
  end

  def run
    sanitize_params!
    @page = Page.find(@params[:page_id])
    build_call_record
    validate_target if errors.blank?
    validate_call_tool if errors.blank?

    if errors.blank?
      if @call.valid?
        Call.transaction do
          @call.action = Action.create!(page: @page, member: @call.member)
          @call.save!
        end
        place_call
      end

      publish_event if @call.persisted? && !@call.failed?
    end

    errors.blank?
  end

  def errors
    call.errors.messages.clone.tap do |e|
      @errors.each do |key, val|
        e[key] ||= []
        e[key] += val
      end
    end
  end

  private

  def sanitize_params!
    if @params[:member_phone_number].present?
      @params[:member_phone_number] = Phony.normalize(@params[:member_phone_number])
    end
  rescue Phony::NormalizationError
  end

  def build_call_record
    non_target_params = {
      page: page,
      member_id: @params[:member_id],
      member_phone_number: @params[:member_phone_number]
    }
    full_params = if valid_manual_target
                    non_target_params.merge(target: manual_target)
                  elsif @params[:target_id].present?
                    non_target_params.merge(target_id: @params[:target_id])
                  else
                    add_error(:base, I18n.t('call_tool.errors.target.missing'))
                    non_target_params.merge(target: {})
                  end

    @call = Call.new(full_params)
  end

  def valid_manual_target
    CallTool::ChecksumValidator.validate(@params[:target_phone_number], @params[:checksum])
  end

  # TODO: Move method to service class, handle error messages in there.
  def place_call
    client = Twilio::REST::Client.new(Settings.twilio.account_sid, Settings.twilio.auth_token)
    params = {
      from: call.caller_id,
      to: call.member_phone_number,
      url: call_start_url(call),
      status_callback: member_call_event_url(call),
      status_callback_method: 'POST',
      status_callback_event: %w[initiated ringing answered completed]
    }
    client.calls.create params
  rescue Twilio::REST::RestError => e
    # 13223: Dial: Invalid phone number format
    # 13224: Dial: Invalid phone number
    # 13225: Dial: Forbidden phone number
    # 13226: Dial: Invalid country code
    # 21211: Invalid 'To' Phone Number
    # 21214: 'To' phone number cannot be reached
    call.action.destroy!
    call.update!(twilio_error_code: e.code, status: 'failed', action_id: nil)
    if (e.code >= 13_223 && e.code <= 13_226) || [21_211, 21_214].include?(e.code)
      add_error(:member_phone_number, I18n.t('call_tool.errors.phone_number.cant_connect'))
    else
      Rails.logger.error("Twilio Error: API responded with code #{e.code} for #{call.attributes.inspect}")
      add_error(:base, I18n.t('call_tool.errors.unknown'))
    end
  end

  def publish_event
    return if @call.member.blank? # handle this in worker

    CallEvent::New.publish(@call, @extra_params)
  end

  # If the targets are updated while the user is on the call tool page, the list
  # of target_ids on the browser are no longer valid.
  # This validation checks for this edge case.
  def validate_target
    add_error(:base, I18n.t('call_tool.errors.target.outdated')) if call.target.blank? && @params[:target_id].present?
  end

  def validate_call_tool
    add_error(:base, 'Please configure a Caller ID before trying to use the call tool') if call.caller_id.blank?
  end

  def add_error(key, message)
    @errors[key] ||= []
    @errors[key] << message
  end

  def manual_target
    {
      phone_number: @params[:target_phone_number],
      phone_extension: @params[:target_phone_extension],
      name: @params[:target_name],
      title: @params[:target_title] || ''
    }
  end
end