lib/bexio_api_client/files.ex

defmodule BexioApiClient.Files do
  @moduledoc """
  Bexio API for the files part of the API
  """

  import BexioApiClient.Helpers
  alias BexioApiClient.SearchCriteria

  alias BexioApiClient.Files.{
    File
  }

  alias BexioApiClient.GlobalArguments
  import BexioApiClient.GlobalArguments, only: [opts_to_query: 1]

  @type tesla_error_type :: BexioApiClient.Helpers.tesla_error_type()

  @doc """
  Fetch a list of files.
  """
  @spec fetch_files(
          client :: Tesla.Client.t(),
          opts :: [GlobalArguments.offset_arg()]
        ) :: {:ok, [File.t()]} | tesla_error_type()
  def fetch_files(client, opts \\ []) do
    bexio_body_handling(
      fn ->
        Tesla.get(client, "/3.0/files", query: opts_to_query(opts))
      end,
      &map_from_files/2
    )
  end

  @doc """
  Search files via query.
  The following search fields are supported:

  * id
  * uuid
  * created_at
  * name
  * extension
  * size_in_bytes
  * mime_type
  * user_id
  * archived?
  * source_id
  """
  @spec search_files(
          client :: Tesla.Client.t(),
          criteria :: list(SearchCriteria.t()),
          opts :: [GlobalArguments.offset_arg()]
        ) :: {:ok, [File.t()]} | tesla_error_type()
  def search_files(
        client,
        criteria,
        opts \\ []
      ) do
    bexio_body_handling(
      fn ->
        Tesla.post(
          client,
          "/3.0/files/search",
          remap(criteria),
          query: opts_to_query(opts)
        )
      end,
      &map_from_files/2
    )
  end

  defp remap([]), do: []
  defp remap(nil), do: nil

  defp remap(keywords) do
    if Keyword.has_key?(keywords, :archived?) do
      keywords
      |> Keyword.put_new(:is_archived, Keyword.get(keywords, :archived?))
      |> Keyword.delete(:arcvived?)
    else
      keywords
    end
  end

  @doc """
  Fetch single file.
  """
  @spec fetch_file(
          client :: Tesla.Client.t(),
          id :: non_neg_integer()
        ) :: {:ok, File.t()} | tesla_error_type()
  def fetch_file(client, id) do
    bexio_body_handling(
      fn ->
        Tesla.get(
          client,
          "/3.0/files/#{id}"
        )
      end,
      &map_from_file/2
    )
  end

  defp map_from_files(files, _env), do: Enum.map(files, &map_from_file/1)

  defp map_from_file(
         %{
           "id" => id,
           "uuid" => uuid,
           "name" => name,
           "size_in_bytes" => size_in_bytes,
           "extension" => extension,
           "mime_type" => mime_type,
           "uploader_email" => uploader_email,
           "user_id" => user_id,
           "is_archived" => archived?,
           "source_type" => source_type,
           "is_referenced" => referenced?,
           "created_at" => created_at
         },
         _env \\ nil
       ) do
    %File{
      id: id,
      uuid: uuid,
      name: name,
      size_in_bytes: size_in_bytes,
      extension: String.to_atom(extension),
      mime_type: mime_type,
      uploader_email: uploader_email,
      user_id: user_id,
      archived?: archived?,
      source_type: String.to_atom(source_type),
      referenced?: referenced?,
      created_at: to_offset_datetime(created_at)
    }
  end

  @doc """
  Download file (original content).
  """
  @spec download_file(
          client :: Tesla.Client.t(),
          id :: non_neg_integer()
        ) :: {:ok, {String.t(), any()}} | tesla_error_type()
  def download_file(client, id) do
    bexio_body_handling(
      fn ->
        Tesla.get(client, "/3.0/files/#{id}/download")
      end,
      fn file, env -> {file, Tesla.get_header(env, "content-type")} end
    )
  end

  @doc """
  Download file (original content).
  """
  @spec preview_file(
          client :: Tesla.Client.t(),
          id :: non_neg_integer()
        ) :: {:ok, {String.t(), any()}} | tesla_error_type()
  def preview_file(client, id) do
    bexio_body_handling(
      fn ->
        Tesla.get(client, "/3.0/files/#{id}/preview")
      end,
      fn file, env -> {file, Tesla.get_header(env, "content-type")} end
    )
  end
end