lib/ecto/upload.ex

defmodule Uploadex.Upload do
  @moduledoc """
  Ecto type that handles upload.

  It stores the filename in the database.
  """

  @behaviour Ecto.Type

  @type upload_path :: %{filename: String.t, path: Path.t, content_type: String.t}
  @type upload_binary :: %{filename: String.t, binary: String.t, content_type: String.t}

  alias Uploadex.FileProcessing

  @impl true
  def type, do: :string

  @spec cast(upload_path | upload_binary) :: {:ok, upload_path} | {:ok, upload_binary} | {:error, keyword()}
  @impl true
  def cast(%{filename: filename, path: path, content_type: content_type}) do
    {:ok, %{
      filename: generate_filename(filename),
      path: path,
      content_type: content_type
    }}
  end

  def cast(%{filename: filename, binary: binary}) do
    case FileProcessing.process_binary(binary) do
      {:ok, %{binary: binary, content_type: content_type}} ->
        {:ok, %{
          filename: generate_filename(filename),
          binary: binary,
          content_type: content_type
        }}

      {:error, message} ->
        {:error, message: message}
    end
  end

  def cast(filename) when is_binary(filename), do: {:ok, filename}

  def cast(_), do: :error

  defp generate_filename(filename), do: Ecto.UUID.generate() <> Path.extname(filename)

  @spec load(any) :: :error | {:ok, binary}
  @impl true
  def load(filename) when is_binary(filename), do: {:ok, filename}
  def load(_), do: :error

  @spec dump(any) :: :error | {:ok, any}
  @impl true
  def dump(%{filename: filename}), do: {:ok, filename}
  def dump(filename) when is_binary(filename), do: {:ok, filename}
  def dump(_), do: :error

  @impl true
  def equal?(file1, file2) do
    get_filename(file1) == get_filename(file2)
  end

  defp get_filename(%{filename: filename}), do: filename
  defp get_filename(filename), do: filename

  @impl true
  def embed_as(_format), do: :dump
end