defmodule FileStorageApi.File do @moduledoc """ Module for uploading deleting and fetching url of file """ @type t :: %__MODULE__{name: String.t(), properties: map} @callback upload(String.t(), String.t(), String.t()) :: {:ok, String.t()} | {:file_upload_error, map | tuple} @callback delete(String.t(), String.t()) :: {:ok, map} | {:error, map} @callback public_url(String.t(), String.t(), DateTime.t(), DateTime.t()) :: {:ok, String.t()} | {:error, String.t()} @callback last_modified(t) :: {:ok, DateTime.t()} | {:error, atom} defstruct name: nil, properties: %{} import FileStorageApi.Base @doc """ Function to upload file has input args container_name: name of the container filename: path to the file with the data to store blob_name: how the blob is going to be called after storage Option field is available that has options for missing container fallback force_container: with false you can disable auto creation of container public: with public on true it will create bucket by default as public cors_policy: can have true or a configuration for configuring cors settings of bucket Returns reference to the file in the asset store """ @spec upload(String.t(), String.t(), String.t(), keyword) :: {:ok, String.t()} | {:file_upload_error, map | tuple} def upload(container_name, filename, blob_name, opts \\ []) do force_container = Keyword.get(opts, :force_container, true) case {api_module(File).upload(container_name, filename, blob_name), force_container} do {{:ok, file}, _} -> {:ok, file} {{:error, :container_not_found}, true} -> container_options = Keyword.take(opts, [:cors_policy, :public]) api_module(Container).create(container_name, Map.new(container_options)) upload(container_name, filename, blob_name, Keyword.put(opts, :force_container, false)) {{:error, error}, _} -> {:file_upload_error, error} end end @doc """ Function to delete files Has 2 inputs container_name: name of container file is stored in filename: reference path of the file stored in the container """ @spec delete(String.t(), String.t()) :: {:ok, map} | {:error, map} def delete(container_name, filename) do api_module(File).delete(container_name, filename) end @doc """ public_url returns an full url to be able to fetch the file with security tokens needed by default 1 day valid """ @spec public_url(String.t(), String.t(), DateTime.t(), DateTime.t()) :: {:ok, String.t()} | {:error, String.t()} def public_url( container_name, file_path, start_time \\ Timex.now(), expire_time \\ Timex.add(Timex.now(), Timex.Duration.from_days(1)) ) do api_module(File).public_url(container_name, file_path, start_time, expire_time) end def last_modified(file), do: api_module(File).last_modified(file) @doc """ This function will create a temporary file and upload to asset store Opts field described at the upload function """ @spec upload_file_from_content(binary, binary, binary | iodata, binary, keyword) :: {:ok, String.t()} | {:file_upload_error, map | tuple} def upload_file_from_content(filename, container_name, content, blob_name, opts \\ []) do Temp.track!() {:ok, dir_path} = Temp.mkdir("file-cache") file_path = Path.join(dir_path, filename) File.write(file_path, content) upload(container_name, file_path, blob_name, opts) after Temp.cleanup() end @spec sanitize(binary) :: binary def sanitize(name) do name |> String.trim() |> Recase.to_kebab() |> String.replace(~r/[^0-9a-z\-]/u, "") |> String.trim("-") end end