Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Twilio integration v1 feature #680

Merged
merged 5 commits into from
Apr 2, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
57 changes: 8 additions & 49 deletions lib/chat_api/conversations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ defmodule ChatApi.Conversations do

alias ChatApi.Accounts.Account
alias ChatApi.Conversations.Conversation
alias ChatApi.Customers
alias ChatApi.Customers.Customer
alias ChatApi.Messages.Message
alias ChatApi.Tags.{Tag, ConversationTag}
Expand Down Expand Up @@ -525,54 +524,14 @@ defmodule ChatApi.Conversations do
|> Repo.update()
end

@spec find_or_create_customer_and_conversation(String.t(), String.t()) ::
{:ok, Customer.t(), Conversation.t()}
| {:error, Ecto.Changeset.t()}
def find_or_create_customer_and_conversation(account_id, phone) do
now = DateTime.utc_now()
sms_source = "sms"

case Customers.list_customers(account_id, %{phone: phone}) do
[] ->
with {:ok, customer} <-
Customers.create_customer(%{
phone: phone,
account_id: account_id,
first_seen: now,
last_seen: now
}),
{:ok, conversation} <-
create_conversation(%{
account_id: account_id,
customer_id: customer.id,
source: sms_source
}) do
{:ok, customer, conversation}
else
error ->
error
end

[customer] ->
with nil <-
find_latest_conversation(account_id, %{
customer_id: customer.id,
source: sms_source
}),
{:ok, conversation} <-
create_conversation(%{
account_id: account_id,
customer_id: customer.id,
source: sms_source
}) do
{:ok, customer, conversation}
else
%Conversation{} = conversation ->
{:ok, customer, conversation}

error ->
error
end
@spec find_or_create_by_customer(String.t(), String.t(), map()) ::
{:ok, Conversation.t()} | {:error, Ecto.Changeset.t()}
def find_or_create_by_customer(account_id, customer_id, attrs \\ %{}) do
params = Map.merge(attrs, %{"customer_id" => customer_id, "account_id" => account_id})

case find_latest_conversation(account_id, params) do
nil -> create_conversation(params)
conversation -> {:ok, conversation}
end
end

Expand Down
27 changes: 27 additions & 0 deletions lib/chat_api/customers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,33 @@ defmodule ChatApi.Customers do
end
end

def find_by_phone(phone, account_id, filters \\ %{}) do
Customer
|> where(account_id: ^account_id, phone: ^phone)
|> where(^filter_where(filters))
|> order_by(desc: :updated_at)
|> first()
|> Repo.one()
end

@spec find_or_create_by_phone(binary() | nil, binary(), map()) ::
{:ok, Customer.t()} | {:error, Ecto.Changeset.t()} | {:error, atom()}
def find_or_create_by_phone(phone, account_id, attrs \\ %{})
def find_or_create_by_phone(nil, _account_id, _attrs), do: {:error, :phone_required}

def find_or_create_by_phone(phone, account_id, attrs) do
case find_by_phone(phone, account_id) do
nil ->
get_default_params()
|> Map.merge(attrs)
|> Map.merge(%{phone: phone, account_id: account_id})
|> create_customer()

customer ->
{:ok, customer}
end
end

@spec create_or_update_by_external_id(binary() | nil, binary(), map()) ::
{:ok, Customer.t()} | {:error, Ecto.Changeset.t()} | {:error, atom()}
def create_or_update_by_external_id(external_id, account_id, attrs \\ %{})
Expand Down
2 changes: 1 addition & 1 deletion lib/chat_api/messages/message.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,6 @@ defmodule ChatApi.Messages.Message do
])
|> validate_required([:account_id, :conversation_id])
|> validate_inclusion(:type, ["reply", "note"])
|> validate_inclusion(:source, ["chat", "slack", "mattermost", "email"])
|> validate_inclusion(:source, ["chat", "slack", "mattermost", "email", "sms"])
end
end
13 changes: 8 additions & 5 deletions lib/chat_api/twilio/notification.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,26 @@ defmodule ChatApi.Twilio.Notification do
Conversations.Conversation,
Customers.Customer,
Twilio,
Twilio.TwilioAuthorization,
Messages.Message
}

@spec notify_sms(ChatApi.Messages.Message.t()) :: {:error, any} | {:ok, Tesla.Env.t()}
def notify_sms(%Message{
conversation_id: conversation_id,
body: body,
account_id: account_id,
customer: %Customer{phone: customer_phone}
Copy link
Contributor

@a8t a8t Apr 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious - looks like most of the refactor here was in order to move this into the with. Is that because we can't be sure that the customer is preloaded? @reichert621

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case, the message is coming from the agent/user, not the customer -- so customer here will be nil... we need to get the customer from the message's conversation instead :)

account_id: account_id
}) do
with conversation <- Conversations.get_conversation!(conversation_id),
with %Conversation{customer: customer} = conversation <-
Conversations.get_conversation!(conversation_id),
{:ok, _} <- validate_source(conversation),
twilio_authorization <- Twilio.get_authorization_by_account(account_id),
%Customer{phone: customer_phone} <- customer,
%TwilioAuthorization{from_phone_number: from_phone_number} = twilio_authorization <-
Twilio.get_authorization_by_account(account_id),
{:ok, _} <- Twilio.Client.validate_phone(customer_phone, twilio_authorization) do
%{
To: customer_phone,
From: twilio_authorization.from_phone_number,
From: from_phone_number,
Body: body
}
|> Twilio.Client.send_message(twilio_authorization)
Expand Down
30 changes: 18 additions & 12 deletions lib/chat_api_web/controllers/twilio_controller.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
defmodule ChatApiWeb.TwilioController do
use ChatApiWeb, :controller

alias ChatApi.Messages
alias ChatApi.Twilio
alias ChatApi.Conversations
alias ChatApi.{Conversations, Customers, Messages, Twilio}
alias ChatApi.Twilio.TwilioAuthorization

require Logger
Expand Down Expand Up @@ -73,15 +71,23 @@ defmodule ChatApiWeb.TwilioController do
twilio_account_sid: account_sid,
from_phone_number: to
}),
{:ok, customer, conversation} <-
Conversations.find_or_create_customer_and_conversation(account_id, from),
{:ok, _mesage} <-
Messages.create_message(%{
body: body,
account_id: account_id,
customer_id: customer.id,
conversation_id: conversation.id
}) do
{:ok, customer} <- Customers.find_or_create_by_phone(from, account_id),
{:ok, conversation} <-
Conversations.find_or_create_by_customer(account_id, customer.id, %{"source" => "sms"}) do
%{
body: body,
account_id: account_id,
customer_id: customer.id,
conversation_id: conversation.id,
source: "sms"
}
|> Messages.create_and_fetch!()
|> Messages.Notification.broadcast_to_admin!()
|> Messages.Notification.notify(:webhooks)
|> Messages.Notification.notify(:slack)
|> Messages.Notification.notify(:mattermost)
|> Messages.Helpers.handle_post_creation_conversation_updates()

send_resp(conn, 200, "")
else
nil ->
Expand Down
60 changes: 0 additions & 60 deletions test/chat_api/conversations_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -771,66 +771,6 @@ defmodule ChatApi.ConversationsTest do
end
end

describe "find_or_create_customer_and_conversation/2" do
test "returns customer and new conversation when customer exists" do
account = insert(:account)
customer = insert(:customer, account: account)

{:ok, found_customer, found_conversation} =
Conversations.find_or_create_customer_and_conversation(account.id, customer.phone)

assert customer.id == found_customer.id

assert customer.id == found_conversation.customer_id
assert "sms" == found_conversation.source
end

test "returns new customer and conversation when customer doesn't exist" do
account = insert(:account)
phone_number = "+18675309"

{:ok, found_customer, found_conversation} =
Conversations.find_or_create_customer_and_conversation(account.id, phone_number)

assert phone_number == found_customer.phone
assert account.id == found_customer.account_id
assert account.id == found_conversation.account_id
end

test "returns latest conversations when multiple conversations exist" do
account = insert(:account)
customer = insert(:customer, account: account)

insert(:conversation,
account: account,
customer: customer,
inserted_at: ~N[2020-12-01 00:00:00],
source: "sms"
)

insert(:conversation,
account: account,
customer: customer,
inserted_at: ~N[2020-12-01 00:01:00],
source: "sms"
)

latest_conversation =
insert(:conversation,
account: account,
customer: customer,
inserted_at: ~N[2020-12-01 00:02:00],
source: "sms"
)

{:ok, found_customer, found_conversation} =
Conversations.find_or_create_customer_and_conversation(account.id, "+18675309")

assert latest_conversation.id == found_conversation.id
assert customer.id == found_customer.id
end
end

defp days_ago(days) do
DateTime.utc_now()
|> DateTime.add(days * 60 * 60 * 24 * -1)
Expand Down
5 changes: 3 additions & 2 deletions test/chat_api/twilio_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ defmodule ChatApi.TwilioTest do
setup do
account = insert(:account)
customer = insert(:customer, account: account)
authorization = insert(:twilio_authorization, account: account)
user = insert(:user, account: account, email: "[email protected]")

{:ok, account: account, customer: customer, user: user}
{:ok, account: account, customer: customer, user: user, authorization: authorization}
end

test "Notification.notify_sms/2 errors when not an sms conversation", %{
Expand All @@ -39,7 +40,7 @@ defmodule ChatApi.TwilioTest do
} do
customer = insert(:customer, account: account, phone: @invalid_phone)
conversation = insert(:conversation, account: account, customer: customer, source: "sms")
message = insert(:message, conversation: conversation, customer: customer)
message = insert(:message, conversation: conversation, customer: customer, account: account)

with_mock Twilio.Client,
send_message: fn _, _ ->
Expand Down
Loading