lib/supabase/connection.ex

defmodule Supabase.Connection do
  @moduledoc """
  Defines the connection to Supabase, it is an Agent that holds the connection
  information and the current bucket.

  To start the connection you need to call `Supabase.Connection.start_link/1`:

      iex> Supabase.Connection.start_link(name: :my_conn, conn_info: %{base_url: "https://myapp.supabase.io", api_key: "my_api_key"})
      {:ok, #PID<0.123.0>}

  But usually you would add the connection to your supervision tree:

      defmodule MyApp.Application do
        use Application

        def start(_type, _args) do
          conn_info = %{base_url: "https://myapp.supabase.io", api_key: "my_api_key"}

          children = [
            {Supabase.Connection, conn_info: conn_info, name: :my_conn}
          ]

          opts = [strategy: :one_for_one, name: MyApp.Supervisor]
          Supervisor.start_link(children, opts)
        end
      end

  Once the connection is started you can use it to perform operations on the
  storage service, for example to list all the buckets:

      iex> conn = Supabase.Connection.fetch_current_bucket!(:my_conn)
      iex> Supabase.Storage.list_buckets(conn)
      {:ok, [
        %Supabase.Storage.Bucket{
          allowed_mime_types: nil,
          file_size_limit: nil,
          id: "my-bucket-id",
          name: "my-bucket",
          public: true
        }
      ]}

  Notice that you can start multiple connections, each one with different
  credentials, and you can use them to perform operations on different buckets!

  ## Fields

  Currently the connection holds the following fields:

  - `:base_url` - The base url of the Supabase API, it is usually in the form
    `https://<app-name>.supabase.io`.
  - `:api_key` - The API key used to authenticate requests to the Supabase API.
  - `:access_token` - Token with specific permissions to access the Supabase API, it is usually the same as the API key.
  - `name`: Simple field to track the name of the connection, started by `start_link/1`.
  - `alias`: Field to easily manage multiple connections on a `Supabase.Client` Agent.
  """

  use Agent
  use Ecto.Schema

  alias Supabase.MissingSupabaseConfig

  @type t :: %__MODULE__{
          base_url: base_url,
          api_key: api_key,
          access_token: access_token,
          bucket: bucket
        }

  @type params :: [
          name: atom,
          conn_info: %{
            base_url: base_url,
            api_key: api_key,
            access_token: access_token,
            bucket: bucket
          }
        ]

  @type base_url :: String.t()
  @type api_key :: String.t()
  @type access_token :: String.t()
  @type bucket :: struct

  @primary_key false
  embedded_schema do
    field(:alias, Supabase.Types.Atom)
    field(:name, Supabase.Types.Atom)
    field(:base_url, :string)
    field(:api_key, :string)
    field(:access_token, :string)
    field(:bucket, :map)
  end

  def start_link(args) do
    name = Keyword.fetch!(args, :name)
    conn_info = Keyword.fetch!(args, :conn_info)

    Agent.start_link(fn -> parse_init_args!(conn_info) end, name: name)
  end

  defp parse_init_args!(conn_info) do
    base_url = Map.get(conn_info, :base_url) || raise MissingSupabaseConfig, :url
    api_key = Map.get(conn_info, :api_key) || raise MissingSupabaseConfig, :key
    access_token = Map.get(conn_info, :access_token, api_key)
    bucket = Map.get(conn_info, :bucket)
    alias = Map.get(conn_info, :alias)
    name = Map.get(conn_info, :name)

    %__MODULE__{
      alias: alias,
      name: name,
      base_url: base_url,
      api_key: api_key,
      access_token: access_token,
      bucket: bucket
    }
  end

  def fetch_current_bucket!(conn) do
    Agent.get(conn, &Map.get(&1, :bucket)) ||
      raise "No current bucket configured on connection #{inspect(conn)}"
  end

  def get_base_url(conn) do
    Agent.get(conn, &Map.get(&1, :base_url))
  end

  def get_api_key(conn) do
    Agent.get(conn, &Map.get(&1, :api_key))
  end

  def get_access_token(conn) do
    Agent.get(conn, &Map.get(&1, :access_token))
  end

  def put_access_token(conn, token) do
    Agent.update(conn, &Map.put(&1, :access_token, token))
  end

  def put_current_bucket(conn, bucket) do
    Agent.update(conn, &Map.put(&1, :bucket, bucket))
  end

  def remove_current_bucket(conn) do
    Agent.update(conn, &Map.delete(&1, :bucket))
  end

  def retrieve_connection(name) do
    Agent.get(name, & &1)
  end
end