lib/supabase/storage/handlers/bucket_handler.ex

defmodule Supabase.Storage.BucketHandler do
  @moduledoc """
  Provides low-level API functions for managing Supabase Storage buckets.

  The `BucketHandler` module offers a collection of functions that directly interact with the Supabase Storage API for managing buckets. This module works closely with the `Supabase.Fetcher` for sending HTTP requests and the `Supabase.Storage.Cache` for caching bucket information.

  ## Features

  - **Bucket Listing**: Fetch a list of all the buckets available in the storage.
  - **Bucket Retrieval**: Retrieve detailed information about a specific bucket.
  - **Bucket Creation**: Create a new bucket with specified attributes.
  - **Bucket Update**: Modify the attributes of an existing bucket.
  - **Bucket Emptying**: Empty the contents of a bucket without deleting the bucket itself.
  - **Bucket Deletion**: Permanently remove a bucket and its contents.

  ## Caution

  This module provides a low-level interface to Supabase Storage buckets and is designed for internal use by the `Supabase.Storage` module. Direct use is discouraged unless you need to perform custom or unsupported actions that are not available through the higher-level API. Incorrect use can lead to unexpected results or data loss.

  ## Implementation Details

  All functions within the module expect a base URL, API key, and access token as their initial arguments, followed by any additional arguments required for the specific operation. Responses are usually in the form of `{:ok, result}` or `{:error, message}` tuples.
  """

  alias Supabase.Connection, as: Conn
  alias Supabase.Fetcher
  alias Supabase.Storage.Bucket
  alias Supabase.Storage.Cache
  alias Supabase.Storage.Endpoints

  @type bucket_id :: String.t()
  @type bucket_name :: String.t()
  @type create_attrs :: %{
          id: String.t(),
          name: String.t(),
          file_size_limit: integer | nil,
          allowed_mime_types: list(String.t()) | nil,
          public: boolean
        }
  @type update_attrs :: %{
          public: boolean | nil,
          file_size_limit: integer | nil,
          allowed_mime_types: list(String.t()) | nil
        }

  @spec list(Conn.base_url(), Conn.api_key(), Conn.access_token()) ::
          {:ok, [Bucket.t()]} | {:error, String.t()}
  def list(base_url, api_key, token) do
    url = Fetcher.get_full_url(base_url, Endpoints.bucket_path())
    headers = Fetcher.apply_headers(api_key, token)

    url
    |> Fetcher.get(headers)
    |> case do
      {:ok, body} -> {:ok, Enum.map(body, &Bucket.parse!/1)}
      {:error, msg} -> {:error, msg}
    end
  end

  @spec retrieve_info(Conn.base_url(), Conn.api_key(), Conn.access_token(), bucket_id) ::
          {:ok, Bucket.t()} | {:error, String.t()}
  def retrieve_info(base_url, api_key, token, bucket_id) do
    if bucket = Cache.find_bucket_by_id(bucket_id) do
      {:ok, bucket}
    else
      url = Fetcher.get_full_url(base_url, Endpoints.bucket_path_with_id(bucket_id))
      headers = Fetcher.apply_headers(api_key, token)

      url
      |> Fetcher.get(headers)
      |> case do
        {:ok, body} -> {:ok, Bucket.parse!(body)}
        {:error, msg} -> {:error, msg}
      end
    end
  end

  @spec create(Conn.base_url(), Conn.api_key(), Conn.access_token(), create_attrs) ::
          {:ok, Bucket.t()} | {:error, String.t()}
  def create(base_url, api_key, token, attrs) do
    url = Fetcher.get_full_url(base_url, Endpoints.bucket_path())
    headers = Fetcher.apply_headers(api_key, token)

    url
    |> Fetcher.post(attrs, headers)
    |> case do
      {:ok, resp} -> {:ok, resp}
      {:error, msg} -> {:error, msg}
    end
  end

  @spec update(Conn.base_url(), Conn.api_key(), Conn.access_token(), bucket_id, update_attrs) ::
          {:ok, Bucket.t()} | {:error, String.t()}
  def update(base_url, api_key, token, id, attrs) do
    url = Fetcher.get_full_url(base_url, Endpoints.bucket_path_with_id(id))
    headers = Fetcher.apply_headers(api_key, token)

    url
    |> Fetcher.put(attrs, headers)
    |> case do
      {:ok, message} -> {:ok, message}
      {:error, msg} -> {:error, msg}
    end
  end

  @spec empty(Conn.base_url(), Conn.api_key(), Conn.access_token(), bucket_id) ::
          {:ok, :successfully_emptied} | {:error, String.t()}
  def empty(base_url, api_key, token, id) do
    url = Fetcher.get_full_url(base_url, Endpoints.bucket_path_to_empty(id))
    headers = Fetcher.apply_headers(api_key, token)

    url
    |> Fetcher.post(nil, headers)
    |> case do
      {:ok, _message} -> {:ok, :successfully_emptied}
      {:error, msg} -> {:error, msg}
    end
  end

  @spec delete(Conn.base_url(), Conn.api_key(), Conn.access_token(), bucket_id) ::
          {:ok, String.t()} | {:error, String.t()}
  def delete(base_url, api_key, token, id) do
    url = Fetcher.get_full_url(base_url, Endpoints.bucket_path_with_id(id))
    headers = Fetcher.apply_headers(api_key, token)

    url
    |> Fetcher.delete(nil, headers)
    |> case do
      {:ok, body} -> {:ok, body}
      {:error, msg} -> {:error, msg}
    end
  end
end