app/controllers/external/api/v1/messages_controller.rb

Summary

Maintainability
C
1 day
Test Coverage
class External::Api::V1::MessagesController < External::Api::V1::ApiV1Controller
  YES_PHRASES = %w(yes y yea yeah yup)
  NO_PHRASES = %w(no n nope)

  before_action :check_signature if Rails.env.production?
  protect_from_forgery with: :null_session

  def create
    if (founder = founder_from_from).present?
      create_outgoings founder, to_addrs
    else
      founders = existing_emails Founder, to_addrs
      if founders.present?
        create_incomings founders, from_addr
      else
        handle_response from_addr, to_addrs
      end
    end

    head :ok
  end

  def demo
    return head :ok unless (founder = founder_from_from).present?
    target = TargetInvestor.from_addr!(founder, Mail::Address.new(ENV['DEMO_EMAIL'])).tap(&:investor_opened!)
    DemoMailer.demo_email(founder, target, demo_params[:subject], demo_params['Message-Id']).deliver_later
    head :ok
  end

  def open
    return head :ok unless intro_request.present?
    return head :ok unless recipient.address == intro_request.investor.email
    recipient_target.investor_opened! intro_request.id, intro_request&.email&.id
    intro_request.update!(
      opened_at: DateTime.now,
      open_city: hook_params['city'],
      open_country: hook_params['country'],
      open_device_type: hook_params['device-type'],
    )
    head :ok
  end

  def click
    return head :ok unless intro_request.present?
    return head :ok unless recipient.address == intro_request.investor.email
    recipient_target.investor_clicked! intro_request.id, hook_params['url']
    intro_request.update!(
      open_city: hook_params['city'],
      open_country: hook_params['country'],
      open_device_type: hook_params['device-type'],
    )
    intro_request.add_domain!(hook_params['url'])
    head :ok
  end

  def bounce
    return head :ok unless intro_request.present?
    recipient_target.update! email: nil if recipient_target.email == recipient.address
    intro_request.investor.update! email: nil if intro_request.investor.email == recipient.address
    intro_request.bounce! recipient.address
    head :ok
  end

  def unsubscribe
    return head :ok unless intro_request.present?
    intro_request.investor.update! opted_in: false
    intro_request.decide! false
    head :ok
  end

  private

  def founder_from_from
    @founder_from_from ||= Founder.where(email: from_addr.address).first
  end

  def from_addr
    @from_addr ||= Mail::Address.new(create_params[:From])
  end

  def to_addrs
    @to_addrs ||= begin
      tos = Mail::AddressList.new(create_params[:To]).addresses
      if create_params[:Cc].present?
        tos += Mail::AddressList.new(create_params[:Cc]).addresses
      end
      tos.delete_if { |a| a.address == ENV['MAILGUN_EMAIL'] }
      tos
    end
  end

  def recipient
    @recipient ||= Mail::Address.new(hook_params[:recipient])
  end

  def recipient_target
    @recipient_target ||= TargetInvestor.from_addr(intro_request.founder, recipient)
  end

  def existing_emails(klass, tos)
    tos.inject(klass.none) { |scope, to| scope.or(klass.where(email: to.address)) }
  end

  def text
    create_params['stripped-text']
  end

  def body
    create_params['body-plain']
  end

  def date
    create_params[:Date].present? ? DateTime.parse(create_params[:Date]) : DateTime.now
  end

  def headers
    @headers ||= JSON.parse(create_params['message-headers'] || '[]').to_h
  end

  def sentiment
    @sentiment ||= GoogleCloud::Language.new(text).sentiment
  end

  def intro_request
    @intro_request ||= intro_request_from_header || intro_request_from_body
  end

  def intro_request_from_header
    token = hook_params['intro_request_token'] || (headers['X-Mailgun-Variables'] || {})['intro_request_token'] || headers['X-VCWiz-Intro-Request']
    Message.intro_request token
  end

  def intro_request_from_body
    Message.intro_request body
  end

  def handle_response(from, tos)
    investor = Investor.where(email: from.address).first
    return unless investor.present?
    return unless intro_request.present?

    if intro_request.decided?
      intro_request.update! reason: text
      return
    end

    if YES_PHRASES.any? { |s| s == text.downcase }
      intro_request.decide! true
    elsif NO_PHRASES.any? { |s| s == text.downcase }
      intro_request.decide! false
    end
  end

  def create_outgoings(founder, tos)
    existing = existing_emails Investor, tos
    if existing.present?
      existing.each { |investor| create_outgoing_from_investor(founder, investor) }
    else
      tos.each { |to| create_outgoing_from_addr(founder, to) }
    end
  end

  def create_outgoing_from_investor(founder, investor)
    target = TargetInvestor.from_investor! founder, investor
    target.email ||= investor.email
    target.save! if target.changed?
    create_outgoing_from_target founder, target
  end

  def create_outgoing_from_addr(founder, to)
    target = TargetInvestor.from_addr! founder, to
    target.email ||= to.address
    target.save! if target.changed?
    create_outgoing_from_target founder, target
  end

  def create_outgoing_from_target(founder, target)
    stage = if TargetInvestor.stages[target.stage] <= TargetInvestor::RAW_STAGES.keys.index(:respond)
      TargetInvestor::RAW_STAGES.keys.index(:waiting)
    else
      TargetInvestor.stages[target.stage]
    end

    founder.connect_to! target, :email if target.last_name.present?

    if target.investor.present?
      email = Email.where(email_id: create_params['Message-Id']).first_or_create!(
        intro_request: intro_request,
        founder: founder,
        investor: target.investor,
        company: founder.primary_company,
        direction: :outgoing,
        old_stage: target.stage,
        new_stage: stage,
        sentiment_score: sentiment&.score,
        sentiment_magnitude: sentiment&.magnitude,
        body: text,
        subject: create_params[:Subject],
        created_at: date,
      )
      return unless email.id_previously_changed?
    end

    target.stage = stage
    target.save!
  end

  def create_incomings(founders, from)
    founders.each { |founder| create_incoming(founder, from) }
  end

  def create_incoming(founder, from)
    target = TargetInvestor.from_addr! founder, from

    stage = if TargetInvestor.stages[target.stage] <= TargetInvestor::RAW_STAGES.keys.index(:respond)
      Message.stage(text, sentiment)
    else
      TargetInvestor.stages[target.stage]
    end

    founder.connect_from! target, :email if target.last_name.present?

    if target.investor.present?
      email = Email.where(email_id: create_params['Message-Id']).first_or_create!(
        intro_request: intro_request,
        founder: founder,
        investor: target.investor,
        company: founder.primary_company,
        direction: :incoming,
        old_stage: target.stage,
        new_stage: stage,
        sentiment_score: sentiment&.score,
        sentiment_magnitude: sentiment&.magnitude,
        body: text,
        subject: create_params[:Subject],
        created_at: date,
      )
      return unless email.id_previously_changed?
      target.investor_replied!(intro_request&.id, email.id).tap do |event|
        event.update! created_at: date
      end
    end

    target.email ||= from.address
    target.stage = stage
    target.save!
  end

  def hook_params
    params.permit('intro_request_token', 'country', 'city', 'device-type', 'url', 'recipient')
  end

  def create_params
    params.permit(:To, :From, :Cc, :Subject, :Date, 'stripped-text', 'body-plain', 'message-headers', 'Message-Id')
  end

  def demo_params
    params.permit(:subject, 'Message-Id')
  end

  def check_api_auth!
    # allow all
  end

  def check_signature
    digest = OpenSSL::Digest::SHA256.new
    signature = OpenSSL::HMAC.hexdigest(digest, ENV['MAILGUN_API_KEY'], [params[:timestamp], params[:token]].join)
    head :unauthorized unless params[:signature] == signature
  end
end