Skip to content
This repository was archived by the owner on Dec 3, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ gem 'pundit'
gem 'rack-cors'
gem 'rails', '~> 5.0.2'
gem 'redis', '~> 3.0'
gem 'sendgrid-ruby'
gem 'sentry-raven'
gem 'sidekiq'

Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ GEM
redis (3.3.3)
responders (2.3.0)
railties (>= 4.2.0, < 5.1)
ruby_http_client (3.0.2)
sendgrid-ruby (4.0.8)
ruby_http_client (~> 3.0.1)
sentry-raven (2.5.3)
faraday (>= 0.7.6, < 1.0)
sidekiq (4.2.10)
Expand Down Expand Up @@ -210,6 +213,7 @@ DEPENDENCIES
rack-cors
rails (~> 5.0.2)
redis (~> 3.0)
sendgrid-ruby
sentry-raven
sidekiq
spring
Expand Down
7 changes: 7 additions & 0 deletions app/jobs/add_user_to_send_grid_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddUserToSendGridJob < ApplicationJob
queue_as :default

def perform(user)
SendGridClient.new.add_user(user)
end
end
50 changes: 50 additions & 0 deletions app/lib/send_grid_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
class SendGridClient
LIST_ID = 1581794

class SendGridClientError < StandardError; end

def initialize
@sendgrid = SendGrid::API.new(api_key: api_key)
self
end

def add_user(user)
Rails.logger.info("Adding user #{user.email} (#{user.id})")
recipient_id = add_recipient(user.attributes.slice(*User::SENDGRID_ATTRIBUTES))
add_recipient_to_list(recipient_id)
rescue => e
Rails.logger.error "Failed to add user #{user.id}: #{e}"
Rails.logger.error e.backtrace.join("\n")
end

private

def add_recipient(data)
response = Response.new(add_recipient_request(data))
Rails.logger.info("Response: #{response.body}")
raise(SendGridClientError, response.body) unless response.successful?
response.body['persisted_recipients']
end

def add_recipient_to_list(recipient_id)
response = Response.new(add_recipient_to_list_request(recipient_id))
Rails.logger.info("Response: #{response.body}")
raise(SendGridClientError, response.body) unless response.successful?
true
end

def api_key
OperationCode.fetch_secret_with(name: :sendgrid_api_key)
end

# The method chains in these request methods represents the path of the API request.
# SendGrid calls this it's Fluent Interface
# https://github.com/sendgrid/sendgrid-ruby#general-v3-web-api-usage-with-fluent-interface
def add_recipient_request(data)
@sendgrid.client.contactdb.recipients.post(request_body: [data])
end

def add_recipient_to_list_request(recipient_id)
@sendgrid.client.contactdb.lists._(LIST_ID).recipients._(recipient_id).post()
end
end
26 changes: 26 additions & 0 deletions app/lib/send_grid_client/response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class SendGridClient::Response
attr_reader :body, :status_code

def initialize(response)
@response = response
@body = JSON.parse(response.body) unless response.body.blank?
@status_code = response.status_code
end

def successful?
successful_status_code && successful_error_count
end

private

def successful_status_code
status_code == '201'
end

# Some requests don't return a body so we default to just checking
# the status code
def successful_error_count
return true if body.blank?
body['error_count'] == 0
end
end
8 changes: 8 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class User < ApplicationRecord
:recoverable, :rememberable, :trackable, :validatable
geocoded_by :zip

# These attributes are what we send to sendgrid as custom fields
SENDGRID_ATTRIBUTES = %w[first_name last_name email]

after_create :welcome_user
before_save :geocode, if: ->(v) { v.zip.present? && v.zip_changed? }

Expand All @@ -20,6 +23,7 @@ def welcome_user
invite_to_slack
add_to_mailchimp
add_to_airtables
add_to_send_grid
end

def invite_to_slack
Expand All @@ -34,6 +38,10 @@ def add_to_airtables
AddUserToAirtablesJob.perform_later(self)
end

def add_to_send_grid
AddUserToSendGridJob.perform_later(self)
end

def token
JsonWebToken.encode(user_id: self.id, roles: [])
end
Expand Down
1 change: 1 addition & 0 deletions run/secrets/sendgrid_api_key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test_sendgrid_api_key
9 changes: 9 additions & 0 deletions test/jobs/add_user_to_send_grid_job_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'test_helper'

class AddUserToSendGridJobTest < ActiveJob::TestCase
test 'it adds a user to send grid' do
user = FactoryGirl.build(:user)
SendGridClient.any_instance.expects(:add_user).with(user)
AddUserToSendGridJob.perform_now(user)
end
end
45 changes: 45 additions & 0 deletions test/lib/send_grid_client_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require 'test_helper'

class SendGridClientTest < ActiveSupport::TestCase
test 'it adds a user to sendgrid' do
user = FactoryGirl.build(:user)
mock_sendgrid_client_for(user)
assert SendGridClient.new.add_user(user)
end

# Sendgrid's gem uses a TON of chained methods. This mess is stubbing out all of them
# while checking to make sure we get sane parameters
def mock_sendgrid_client_for(user)
mock_response = mock
mock_response.expects(:status_code).returns('201').at_least_once
mock_response.stubs(:body).returns('{ "error_count": 0, "persisted_recipients": [ "1234" ] }')

mock_recipients = mock
mock_recipients.expects(:post).with(request_body: ['first_name' => user.first_name, 'last_name' => user.last_name, 'email' => user.email]).returns(mock_response)

mock_list_response = mock
mock_list_response.stubs(:status_code).returns('201')
mock_list_response.stubs(:body).returns(nil)

mock_list_post = mock
mock_list_post.stubs(:post).returns(mock_list_response)

mock_recipient_id = mock
mock_recipient_id.stubs(:_).with(['1234']).returns(mock_list_post)

mock_list_id = mock
mock_list_id.stubs(:recipients).returns(mock_recipient_id)

mock_lists = mock
mock_lists.stubs(:_).with(SendGridClient::LIST_ID).returns(mock_list_id)

mock_contactdb = mock
mock_contactdb.stubs(:recipients).returns(mock_recipients)
mock_contactdb.stubs(:lists).returns(mock_lists)

mock_client = mock
mock_client.stubs(:contactdb).returns(mock_contactdb)

SendGrid::API.any_instance.stubs(:client).returns(mock_client)
end
end
1 change: 1 addition & 0 deletions test/models/user_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def setup
SlackJobs::InviterJob.expects(:perform_later).with(user_opts[:email])
MailchimpInviterJob.expects(:perform_later).with(email: user_opts[:email])
AddUserToAirtablesJob.expects(:perform_later).with(user)
AddUserToSendGridJob.expects(:perform_later).with(user)

assert_difference('User.count') { user.save }
end
Expand Down