lib/swoosh/adapters/local/storage/memory.ex

defmodule Swoosh.Adapters.Local.Storage.Memory do
  @moduledoc ~S"""
  In-memory storage driver used by the
  [Swoosh.Adapters.Local](Swoosh.Adapters.Local.html) module.

  The emails in this mailbox are stored in memory and won't persist once your
  application is stopped.
  """

  use GenServer

  @doc """
  Starts the server
  """
  def start_link(args \\ []) do
    case GenServer.start_link(__MODULE__, args, name: {:global, __MODULE__}) do
      {:ok, pid} ->
        {:ok, pid}
      {:error, {:already_started, pid}} ->
        Process.link(pid)
        {:ok, pid}
    end
  end

  @doc """
  Stops the server
  """
  def stop() do
    GenServer.stop({:global, __MODULE__})
  end

  @doc ~S"""
  Push a new email into the mailbox.

  In order to make it easy to fetch a single email, a `Message-ID` header is
  added to the email before being stored.

  ## Examples

      iex> email = new |> from("tony.stark@example.com")
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, [...]}
      iex> Memory.push(email)
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, headers: %{"Message-ID": "a1b2c3"}, [...]}
  """
  def push(email) do
    GenServer.call({:global, __MODULE__}, {:push, email})
  end

  @doc ~S"""
  Pop the last email from the mailbox.

  ## Examples

      iex> email = new |> from("tony.stark@example.com")
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, [...]}
      iex> Memory.push(email)
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, headers: %{"Message-ID": "a1b2c3"}, [...]}
      iex> Memory.all() |> Enum.count()
      1
      iex> Memory.pop()
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, headers: %{"Message-ID": "a1b2c3"}, [...]}
      iex> Memory.all() |> Enun.count()
      0
  """
  def pop() do
    GenServer.call({:global, __MODULE__}, :pop)
  end

  @doc ~S"""
  Get a specific email from the mailbox.

  ## Examples

      iex> email = new |> from("tony.stark@example.com")
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, [...]}
      iex> Memory.push(email)
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, headers: %{"Message-ID": "a1b2c3"}, [...]}
      iex> Memory.get("A1B2C3")
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, headers: %{"Message-ID": "a1b2c3"}, [...]}
  """
  def get(id) do
    GenServer.call({:global, __MODULE__}, {:get, id})
  end

  @doc ~S"""
  List all the emails in the mailbox.

  ## Examples

      iex> email = new |> from("tony.stark@example.com")
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, [...]}
      iex> Memory.push(email)
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, headers: %{"Message-ID": "a1b2c3"}, [...]}
      iex> Memory.all()
      [%Swoosh.Email{from: {"", "tony.stark@example.com"}, headers: %{"Message-ID": "a1b2c3"}, [...]}]
  """
  def all() do
    GenServer.call({:global, __MODULE__}, :all)
  end

  @doc ~S"""
  Delete all the emails currently in the mailbox.

  ## Examples

      iex> email = new |> from("tony.stark@example.com")
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, [...]}
      iex> Memory.push(email)
      %Swoosh.Email{from: {"", "tony.stark@example.com"}, headers: %{"Message-ID": "a1b2c3"}, [...]}
      iex> Memory.delete_all()
      :ok
      iex> Memory.list()
      []
  """
  def delete_all() do
    GenServer.call({:global, __MODULE__}, :delete_all)
  end

  # Callbacks

  def init(_args) do
    {:ok, []}
  end

  def handle_call({:push, email}, _from, emails) do
    id = :crypto.strong_rand_bytes(16) |> Base.encode16() |> String.downcase()
    email =
      email
      |> Swoosh.Email.header("Message-ID", id)
      |> Swoosh.Email.put_private(:sent_at, DateTime.utc_now() |> DateTime.to_iso8601())

    attachments_with_data =
      Enum.map(email.attachments, fn attachment ->
        %{attachment | data: Swoosh.Attachment.get_content(attachment)}
      end)

    email = %{email | attachments: attachments_with_data}
    {:reply, email, [email | emails]}
  end

  def handle_call(:pop, _from, [h | t]) do
    {:reply, h, t}
  end

  def handle_call({:get, id}, _from, emails) do
    email =
      Enum.find(emails, fn %{headers: %{"Message-ID" => msg_id}} ->
        msg_id == String.downcase(id)
      end)

    {:reply, email, emails}
  end

  def handle_call(:all, _from, emails) do
    {:reply, emails, emails}
  end

  def handle_call(:delete_all, _from, _emails) do
    {:reply, :ok, []}
  end
end