179 lines
4.9 KiB
Elixir
179 lines
4.9 KiB
Elixir
|
if Code.ensure_loaded?(Plug) do
|
||
|
defmodule Plug.Swoosh.MailboxPreview do
|
||
|
@moduledoc """
|
||
|
Plug that serves pages useful for previewing emails in development.
|
||
|
|
||
|
## Examples
|
||
|
|
||
|
# in a Phoenix router
|
||
|
defmodule Sample.Router do
|
||
|
scope "/dev" do
|
||
|
pipe_through [:browser]
|
||
|
forward "/mailbox", Plug.Swoosh.MailboxPreview
|
||
|
end
|
||
|
end
|
||
|
"""
|
||
|
|
||
|
use Plug.Router
|
||
|
use Plug.ErrorHandler
|
||
|
|
||
|
alias Swoosh.Email.Render
|
||
|
alias Swoosh.Adapters.Local.Storage.Memory
|
||
|
|
||
|
require EEx
|
||
|
|
||
|
EEx.function_from_file(
|
||
|
:defp,
|
||
|
:template,
|
||
|
"lib/plug/templates/mailbox_viewer/index.html.eex",
|
||
|
[:assigns]
|
||
|
)
|
||
|
|
||
|
def call(conn, opts) do
|
||
|
conn =
|
||
|
conn
|
||
|
|> assign(:base_path, Path.join(["/" | conn.script_name]))
|
||
|
|> assign(:storage_driver, opts[:storage_driver] || Memory)
|
||
|
|
||
|
super(conn, opts)
|
||
|
end
|
||
|
|
||
|
plug :match
|
||
|
plug :dispatch
|
||
|
|
||
|
post "/clear" do
|
||
|
conn.assigns.storage_driver.delete_all()
|
||
|
|
||
|
conn
|
||
|
|> put_resp_header("location", conn.assigns.base_path)
|
||
|
|> send_resp(302, ~c"")
|
||
|
end
|
||
|
|
||
|
get "/json" do
|
||
|
emails = Enum.map(conn.assigns.storage_driver.all(), &render_email_json/1)
|
||
|
response = Swoosh.json_library().encode_to_iodata!(%{data: emails})
|
||
|
|
||
|
conn
|
||
|
|> put_resp_content_type("application/json")
|
||
|
|> send_resp(200, response)
|
||
|
end
|
||
|
|
||
|
get "/" do
|
||
|
emails = conn.assigns.storage_driver.all()
|
||
|
|
||
|
conn
|
||
|
|> put_resp_content_type("text/html")
|
||
|
|> send_resp(200, template(emails: emails, email: nil, conn: conn))
|
||
|
end
|
||
|
|
||
|
get "/:id/html" do
|
||
|
email = conn.assigns.storage_driver.get(id)
|
||
|
|
||
|
conn
|
||
|
|> put_resp_content_type("text/html")
|
||
|
|> send_resp(200, replace_inline_references(email))
|
||
|
end
|
||
|
|
||
|
get "/:id/attachments/:index" do
|
||
|
index = String.to_integer(index)
|
||
|
|
||
|
id
|
||
|
|> conn.assigns.storage_driver.get()
|
||
|
|> case do
|
||
|
%{attachments: attachments} when length(attachments) > index ->
|
||
|
attachments
|
||
|
|> Enum.at(index)
|
||
|
|> case do
|
||
|
%{data: data, content_type: content_type} when not is_nil(data) ->
|
||
|
conn
|
||
|
|> put_resp_content_type(content_type)
|
||
|
|> send_resp(200, data)
|
||
|
|
||
|
%{path: path, content_type: content_type} when not is_nil(path) ->
|
||
|
conn
|
||
|
|> put_resp_content_type(content_type)
|
||
|
|> send_resp(200, File.read!(path))
|
||
|
|
||
|
_ ->
|
||
|
conn
|
||
|
|> put_resp_content_type("text/html")
|
||
|
|> send_resp(500, "Attachment cannot be displayed")
|
||
|
end
|
||
|
|
||
|
_ ->
|
||
|
conn
|
||
|
|> put_resp_content_type("text/html")
|
||
|
|> send_resp(404, "Attachment Not Found")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
get "/:id" do
|
||
|
emails = conn.assigns.storage_driver.all()
|
||
|
email = conn.assigns.storage_driver.get(id)
|
||
|
|
||
|
conn
|
||
|
|> put_resp_content_type("text/html")
|
||
|
|> send_resp(200, template(emails: emails, email: email, conn: conn))
|
||
|
end
|
||
|
|
||
|
defp render_email_json(email) do
|
||
|
%{
|
||
|
subject: email.subject,
|
||
|
from: Render.render_recipient(email.from),
|
||
|
to: Enum.map(email.to, &Render.render_recipient/1),
|
||
|
cc: Enum.map(email.cc, &Render.render_recipient/1),
|
||
|
bcc: Enum.map(email.bcc, &Render.render_recipient/1),
|
||
|
reply_to: Render.render_recipient(email.reply_to),
|
||
|
sent_at: Map.get(email.private, :sent_at),
|
||
|
text_body: email.text_body,
|
||
|
html_body: email.html_body,
|
||
|
headers: email.headers,
|
||
|
provider_options:
|
||
|
Enum.map(email.provider_options, fn {k, v} -> %{key: k, value: inspect(v)} end),
|
||
|
attachments: Enum.map(email.attachments, &render_attachment_json/1)
|
||
|
}
|
||
|
end
|
||
|
|
||
|
defp render_attachment_json(attachment) do
|
||
|
%{
|
||
|
filename: attachment.filename,
|
||
|
content_type: attachment.content_type,
|
||
|
path: attachment.path,
|
||
|
type: attachment.type,
|
||
|
headers: Map.new(attachment.headers)
|
||
|
}
|
||
|
end
|
||
|
|
||
|
defp to_absolute_url(conn, path) do
|
||
|
Path.join(conn.assigns.base_path, path)
|
||
|
end
|
||
|
|
||
|
defp render_recipient(recipient) do
|
||
|
case Render.render_recipient(recipient) do
|
||
|
"" -> "n/a"
|
||
|
recipient -> Plug.HTML.html_escape(recipient)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp render_value(value) when value == "", do: "n/a"
|
||
|
|
||
|
defp render_value(value), do: Plug.HTML.html_escape(value)
|
||
|
|
||
|
defp replace_inline_references(%{html_body: nil, text_body: text_body}) do
|
||
|
text_body
|
||
|
end
|
||
|
|
||
|
defp replace_inline_references(%{html_body: html_body, attachments: attachments}) do
|
||
|
~r/"cid:([^"]*)"/
|
||
|
|> Regex.scan(html_body)
|
||
|
|> Enum.reduce(html_body, fn [_, ref], body ->
|
||
|
with index when is_integer(index) <- Enum.find_index(attachments, &(&1.filename == ref)) do
|
||
|
String.replace(body, "cid:#{ref}", "attachments/#{index}")
|
||
|
else
|
||
|
nil -> html_body
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
end
|
||
|
end
|