Skip to content

Commit

Permalink
refactor: migrate from ExAws to aws-elixir (#2167)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecristen authored Sep 11, 2024
1 parent 9842843 commit acd4d50
Show file tree
Hide file tree
Showing 27 changed files with 571 additions and 729 deletions.
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Config

config :elixir, ansi_enabled: true

config :dotcom, :aws, ExAws
config :dotcom, :aws_client, AwsClient.Behaviour

config :dotcom, :cms_api_module, CMS.Api

Expand Down
3 changes: 1 addition & 2 deletions config/dotcom/alerts.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ config :dotcom, alerts_api_mfa: {MBTA.Api.Alerts, :all, []}

if config_env() == :test do
config :dotcom,
alerts_api_mfa: {JsonApi, :empty, []},
alerts_mock_aws_client: Alerts.TestExAws
alerts_api_mfa: {JsonApi, :empty, []}
end
5 changes: 1 addition & 4 deletions config/dotcom/feedback.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ config :dotcom,
if config_env() == :test do
config :dotcom,
time_fetcher: Feedback.FakeDateTime,
exaws_config_fn: &Feedback.Test.mock_config/1,
exaws_perform_fn: &Feedback.Test.mock_perform/2,
feedback_rate_limit: 1_000,
support_ticket_to_email: "[email protected]",
support_ticket_from_email: "[email protected]",
Expand All @@ -17,6 +15,5 @@ end

if config_env() == :dev do
config :dotcom,
exaws_config_fn: &Feedback.MockAws.config/1,
exaws_perform_fn: &Feedback.MockAws.perform/2
send_email_fn: &Feedback.MockAws.send_email/1
end
2 changes: 1 addition & 1 deletion config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ config :dotcom, DotcomWeb.ViewHelpers,

config :recaptcha,
public_key: System.get_env("RECAPTCHA_PUBLIC_KEY"),
secret: System.get_env("RECAPTCHA_PRIVATE_KEY")
secret: System.get_env("RECAPTCHA_PRIVATE_KEY", "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe")

config :sentry,
dsn: System.get_env("SENTRY_DSN"),
Expand Down
6 changes: 5 additions & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Config

config :dotcom, :aws, ExAws.Mock
config :dotcom, :aws_client, AwsClient.Mock

config :dotcom, :cache, Dotcom.Cache.TestCache

Expand Down Expand Up @@ -36,3 +36,7 @@ config :dotcom, :secure_pipeline,
host: nil,
rewrite_on: [:x_forwarded_proto]
]

# Credentials that always show widget and pass backend validation:
config :recaptcha,
http_client: Recaptcha.Http.MockClient
38 changes: 19 additions & 19 deletions lib/alerts/cache/bus_stop_change_s3.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ defmodule Alerts.Cache.BusStopChangeS3 do
@cache Application.compile_env!(:dotcom, :cache)
@ttl :timer.hours(24)

@ex_aws Application.compile_env(:dotcom, [:alerts_mock_aws_client], ExAws)
@ex_aws_s3 Application.compile_env(:dotcom, [:alerts_mock_aws_client], ExAws.S3)
@aws_client Application.compile_env(:dotcom, :aws_client)
@bucket "mbta-dotcom"

@doc """
Expand Down Expand Up @@ -88,18 +87,20 @@ defmodule Alerts.Cache.BusStopChangeS3 do
)
def get_stored_alerts do
keys =
@ex_aws_s3.list_objects(@bucket, prefix: bucket_prefix())
|> @ex_aws.stream!
|> Stream.map(& &1.key)
|> Enum.to_list()
case @aws_client.list_objects(@bucket, bucket_prefix()) do
{:ok, %{"ListBucketResult" => %{"Contents" => objects}}, %{}} ->
objects |> Enum.map(& &1["Key"])

_ ->
[]
end

Enum.map(keys, fn key ->
result =
@ex_aws_s3.get_object(@bucket, key, prefix: bucket_prefix())
|> @ex_aws.request()
@aws_client.get_object(@bucket, key)

case result do
{:ok, %{body: alert_data}} ->
{:ok, %{"Body" => alert_data}, _} ->
decompress_alert(alert_data)

error ->
Expand All @@ -119,15 +120,14 @@ defmodule Alerts.Cache.BusStopChangeS3 do
end)
|> Task.async_stream(
fn {id, contents} ->
aws_operation = @ex_aws_s3.put_object(@bucket, "#{bucket_prefix()}/#{id}", contents)

Logger.info(fn ->
"module=#{__MODULE__} func=write_alerts operation=#{inspect(aws_operation)}"
end)

aws_operation
|> @ex_aws.request()
|> log_result(id, "write_alerts")
case @aws_client.put_object(@bucket, "#{bucket_prefix()}/#{id}", %{"Body" => contents}) do
{:ok, _, _} ->
:ok

error ->
log_result(error, id, "write_alerts")
error
end
end,
max_concurrency: 10
)
Expand All @@ -144,7 +144,7 @@ defmodule Alerts.Cache.BusStopChangeS3 do
:erlang.binary_to_term(alert)
end

defp log_result({:ok, _}, _alert_id, _func_name) do
defp log_result({:ok, _, _}, _alert_id, _func_name) do
:ok
end

Expand Down
96 changes: 96 additions & 0 deletions lib/aws_client/behaviour.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
defmodule AwsClient.Behaviour do
@moduledoc """
The behaviour for interacting with the AWS client
"""
@callback client() :: AWS.Client.t()
@callback search_place_index_for_position(
String.t(),
AWS.Location.search_place_index_for_position_request()
) ::
{:ok, AWS.Location.search_place_index_for_position_response(), any()}
| {:error, {:unexpected_response, any()}}
| {:error, AWS.Location.search_place_index_for_position_errors()}

@callback search_place_index_for_suggestions(
String.t(),
AWS.Location.search_place_index_for_suggestions_request()
) ::
{:ok, AWS.Location.search_place_index_for_suggestions_response(), any()}
| {:error, {:unexpected_response, any()}}
| {:error, AWS.Location.search_place_index_for_suggestions_errors()}

@callback search_place_index_for_text(
String.t(),
AWS.Location.search_place_index_for_text_request()
) ::
{:ok, AWS.Location.search_place_index_for_text_response(), any()}
| {:error, {:unexpected_response, any()}}
| {:error, AWS.Location.search_place_index_for_text_errors()}

@type bucket_name :: String.t()
@type bucket_prefix :: String.t()
@type bucket_object_key :: String.t()
@callback get_object(bucket_name(), bucket_prefix()) ::
{:ok, AWS.S3.get_object_output(), any()}
| {:error, {:unexpected_response, any()}}
| {:error, AWS.S3.get_object_errors()}
@callback put_object(bucket_name(), bucket_object_key(), AWS.S3.put_object_request()) ::
{:ok, AWS.S3.put_object_output(), any()}
| {:error, {:unexpected_response, any()}}
@callback list_objects(bucket_name(), bucket_object_key()) ::
{:ok, AWS.S3.list_objects_output(), any()}
| {:error, {:unexpected_response, any()}}
| {:error, AWS.S3.list_objects_errors()}

@callback send_raw_email(AWS.SES.send_raw_email_request()) ::
{:ok, AWS.SES.send_raw_email_response(), any()}
| {:error, {:unexpected_response, any()}}
| {:error, AWS.SES.send_raw_email_errors()}

@behaviour __MODULE__

@impl __MODULE__
def search_place_index_for_position(index_name, input) do
AWS.Location.search_place_index_for_position(client(), index_name, input)
end

@impl __MODULE__
def search_place_index_for_suggestions(index_name, input) do
AWS.Location.search_place_index_for_suggestions(client(), index_name, input)
end

@impl __MODULE__
def search_place_index_for_text(index_name, input) do
AWS.Location.search_place_index_for_text(client(), index_name, input)
end

@impl __MODULE__
def list_objects(bucket, prefix),
do: AWS.S3.list_objects(client(), bucket, nil, nil, nil, nil, prefix, nil, nil, nil)

@impl __MODULE__
def get_object(bucket, object_key),
do: AWS.S3.get_object(client(), bucket, object_key)

@impl __MODULE__
def put_object(bucket, object_key, contents),
do: AWS.S3.put_object(client(), bucket, object_key, contents)

@impl __MODULE__
def send_raw_email(message), do: AWS.SES.send_raw_email(client(), message)

@impl __MODULE__
def client do
case :aws_credentials.get_credentials() do
%{
access_key_id: access_key_id,
secret_access_key: secret_access_key,
token: token
} ->
AWS.Client.create(access_key_id, secret_access_key, token, "us-east-1")

_ ->
AWS.Client.create("us-east-1")
end
end
end
8 changes: 0 additions & 8 deletions lib/dotcom_web/controllers/customer_support_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -440,14 +440,6 @@ defmodule DotcomWeb.CustomerSupportController do
bus_ferry_cr_options |> Map.put("subway_options", subway_options)
end

@spec get_routes_for_mode(Plug.Conn.t(), atom) :: list
def get_routes_for_mode(conn, mode) do
opts = conn.assigns[:all_options_per_mode]

str = Atom.to_string(mode)
opts["#{str}_options"] || []
end

defp set_service_options(conn, _) do
assign(conn, :service_options, Feedback.Message.service_options())
end
Expand Down
18 changes: 12 additions & 6 deletions lib/feedback/mailer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ defmodule Feedback.Mailer do
alias Feedback.Message
alias Mail.Renderers.RFC2822

@spec send_heat_ticket(Message.t(), [map()]) :: {:ok, any} | {:error, any}
@aws_client Application.compile_env(:dotcom, :aws_client)

@spec send_heat_ticket(Message.t(), [map()]) :: {:ok, any, any} | {:error, any}
def send_heat_ticket(message, photo_info) do
no_request_response = if message.no_request_response, do: "No", else: "Yes"
ada_complaint = if message.ada_complaint, do: "Yes", else: "No"
Expand Down Expand Up @@ -66,14 +68,18 @@ defmodule Feedback.Mailer do
|> Mail.put_subject("MBTA Customer Comment Form")
|> Mail.put_text(body)

exaws_config_fn = Application.get_env(:dotcom, :exaws_config_fn, &ExAws.Config.new/1)

exaws_perform_fn = Application.get_env(:dotcom, :exaws_perform_fn, &ExAws.Operation.perform/2)
send_email_fn =
Application.get_env(
:dotcom,
:send_email_fn,
&@aws_client.send_raw_email/1
)

message
|> RFC2822.render()
|> ExAws.SES.send_raw_email()
|> exaws_perform_fn.(exaws_config_fn.(:ses))
|> Base.encode64()
|> then(&%{"RawMessage" => %{"Data" => &1}})
|> send_email_fn.()
end

@spec topic(Message.t()) :: String.t()
Expand Down
10 changes: 3 additions & 7 deletions lib/feedback/mock_aws.ex
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
defmodule Feedback.MockAws do
@moduledoc """
Mock ExAws functions so that we aren't dependent on AWS in development.
Mock AWS functions so that we aren't dependent on AWS in development.
"""
require Logger

alias Mail.Parsers.RFC2822

def config(:ses) do
:ok
end

def perform(%{params: %{"RawMessage.Data" => raw_message}}, _config) do
def send_email(%{"RawMessage" => %{"Data" => raw_message}}) do
_ =
raw_message
|> Base.decode64!()
|> RFC2822.parse()
|> log_email()

{:ok, :status_info_that_gets_ignored_by_caller}
{:ok, :status_info_that_gets_ignored_by_caller, :ok}
end

defp log_email(%Mail.Message{headers: %{"to" => to}, body: body}) do
Expand Down
4 changes: 2 additions & 2 deletions lib/feedback/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ defmodule Feedback.Repo do
@moduledoc "module to send a HEAT ticket with optional attachments"
require Logger

@spec send_ticket(Feedback.Message.t()) :: {:ok, any} | {:error, any}
@spec send_ticket(Feedback.Message.t()) :: {:ok, any, any} | {:error, any}
def send_ticket(message) do
result = Feedback.Mailer.send_heat_ticket(message, message.photos)

case result do
{:ok, _} ->
{:ok, _, _} ->
Logger.info("module=#{__MODULE__} HEAT Ticket successfully sent.")

{:error, error} ->
Expand Down
15 changes: 5 additions & 10 deletions lib/feedback/test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Feedback.Test do
file = Application.get_env(:dotcom, :test_mail_file)
body = File.read!(file)

case Poison.decode(body) do
case Jason.decode(body) do
{:ok, parsed} ->
parsed

Expand All @@ -16,11 +16,7 @@ defmodule Feedback.Test do
end
end

def mock_config(:ses) do
:ok
end

def mock_perform(%{params: %{"RawMessage.Data" => raw_message}}, _config) do
def send_email(%{"RawMessage" => %{"Data" => raw_message}}) do
parsed_message = raw_message |> Base.decode64!() |> RFC2822.parse()

attachments =
Expand All @@ -30,18 +26,17 @@ defmodule Feedback.Test do
[]
end

message_json =
message =
%{
to: parsed_message.headers["to"],
text: parsed_message.body,
attachments: attachments |> Enum.map(&simplify_attachment/1)
}
|> Poison.encode!()

file = Application.get_env(:dotcom, :test_mail_file)
File.write!(file, message_json)
File.write!(file, Jason.encode!(message))

{:ok, :status_info_that_gets_ignored_by_caller}
{:ok, message, %{}}
end

defp simplify_attachment(%Mail.Message{
Expand Down
Loading

0 comments on commit acd4d50

Please sign in to comment.