defmodule CouchDB do @moduledoc """ Lightweight CouchDB client builder focused on authentication flows. The module exposes helpers to build a pre-configured `Tesla.Client` alongside a thin wrapper around `Tesla.request/2` to keep the original API surface available. """ alias CouchDB.Environment alias Tesla.Middleware @type interface :: :base | :backend @default_headers [{"user-agent", "couchdb_auth"}] @default_adapter {Tesla.Adapter.Hackney, []} @doc """ Build a `Tesla.Client` configured for the requested interface. The `interface` can be `:base` (default) or `:backend`. Additional credential overrides may be supplied via `overrides`. """ @spec client(interface(), map()) :: Tesla.Client.t() def client(interface \\ :base, overrides \\ %{}) do url = interface_url(interface) overrides = Map.new(overrides) admin = Environment.server_admin() credentials = %{ username: Map.get(overrides, :username, admin.user), password: Map.get(overrides, :password, admin.password), url: url } |> Map.merge(Map.drop(overrides, [:username, :password])) middleware = [ {Middleware.BaseUrl, url}, {Middleware.Headers, @default_headers}, {Middleware.JSON, engine: Jason} ] ++ auth_middleware(Environment.auth_type(), credentials, url) Tesla.client(middleware, @default_adapter) end @doc """ Executes an HTTP request using either the provided `%Tesla.Client{}` or a freshly built one. """ @spec request(atom(), String.t(), keyword()) :: {:ok, Tesla.Env.t()} | {:error, any()} def request(method, url, opts \\ []) do {client, opts} = pop_client(opts) request_opts = opts |> Keyword.put(:method, method) |> Keyword.put(:url, url) Tesla.request(client, request_opts) end for verb <- ~w(delete get head options patch post put trace)a do @spec unquote(verb)(String.t(), keyword()) :: {:ok, Tesla.Env.t()} | {:error, any()} def unquote(verb)(url, opts \\ []) do request(unquote(verb), url, opts) end end defp pop_client(opts) do case Keyword.pop(opts, :client) do {nil, rest_opts} -> {interface, rest_opts} = Keyword.pop(rest_opts, :interface, :base) overrides = rest_opts |> Keyword.get(:credentials, %{}) |> Map.new() rest_opts = Keyword.drop(rest_opts, [:credentials]) {client(interface, overrides), rest_opts} {client, rest_opts} -> {client, rest_opts} end end defp interface_url(:backend), do: Environment.backend_url() defp interface_url(_), do: Environment.base_url() defp auth_middleware(:admin_party, _opts, _url), do: [] defp auth_middleware(:basic, opts, _url) do [{Tesla.Middleware.CouchDB.BasicAuth, opts}] end defp auth_middleware(:cookie, opts, url) do [{Tesla.Middleware.CouchDB.CookieAuth, Map.put(opts, :url, url)}] end defp auth_middleware(:oauth, opts, url) do credentials = Environment.oauth_creds() |> Map.merge(%{url: url}) |> Map.merge(opts) [{Tesla.Middleware.CouchDB.OAuth, credentials}] end defp auth_middleware(_, _opts, _url), do: [] end