From 0d4ada6c102ad775e11f33694487173cddce03bc Mon Sep 17 00:00:00 2001 From: href Date: Sun, 11 Dec 2022 00:26:59 +0000 Subject: couch: improve, add post, put --- lib/couch.ex | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 10 deletions(-) (limited to 'lib/couch.ex') diff --git a/lib/couch.ex b/lib/couch.ex index fdd8579..2d75314 100644 --- a/lib/couch.ex +++ b/lib/couch.ex @@ -1,18 +1,94 @@ defmodule Couch do - def get(db, doc) do - config = Application.get_env(:lsg, :couch) - url = [Keyword.get(config, :url), db, doc] |> Enum.join("/") - user = Keyword.get(config, :user) - pass = Keyword.get(config, :pass) - client_options = Keyword.get(config, :client_options, []) - headers = [{"accept", "application/json"}, {"user-agent", "beautte"}] - options = [hackney: [:insecure, {:basic_auth, {user, pass}}]] ++ client_options + @moduledoc """ + Simple, no-frills, CouchDB client + """ + + @type base_error :: {:error, :bad_request} | {:error, :unauthorized} | + {:error, :server_error} | {:error, :service_unavailable} | + {:error, :bad_response} | {:error, HTTPoison.Error.t()} + + def new(db, params \\ []) do + {url, headers, options} = prepare_request([db], [], params) + case HTTPoison.put(url, headers, options) do + {:ok, %HTTPoison.Response{status_code: ok, body: body}} when ok in [201, 202] -> + :ok + {:ok, %HTTPoison.Response{status_code: 412}} -> + {:error, :exists} + error -> + handle_generic_response(error) + end + end + + @doc """ + Retrieve a document `doc` from `db`. + + `params` are [documented here](https://docs.couchdb.org/en/3.2.2-docs/api/document/common.html) + """ + @spec get(String.t(), String.t(), Keyword.t()) :: {:ok, Map.t()} | {:error, :not_found} | {:error, any()} + def get(db, doc, params \\ []) do + {url, headers, options} = prepare_request([db, doc], [], params) case HTTPoison.get(url, headers, options) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> {:ok, Poison.decode!(body)} - {:ok, %HTTPoison.Response{status_code: 404}} -> - {:error, :not_found} error -> {:error, {:couchdb_error, error}} end end + + @spec post(String.t(), Map.t(), Keyword.t()) :: {:error, :operation_failed} | {:error, :not_found} | {:error, :exists} | {:error, any()} + def post(db, data, params \\ []) do + {url, headers, options} = prepare_request([db], [{"content-type", "application/json"}], params) + with \ + {:ok, %HTTPoison.Response{status_code: ok, body: body}} when ok in [201, 202] <- HTTPoison.post(url, Poison.encode!(data), headers, options), + {:json, {:ok, %{"ok" => true, "id" => id, "rev" => rev}}} <- {:json, Poison.decode(body)} do + {:ok, id, rev} + else + {:ok, %HTTPoison.Response{status_code: 409}} -> {:error, :exists} + {:json, {:ok, body}} -> + Logger.error("couch: operation failed: #{inspect body}") + {:error, :operation_failed} + error -> handle_generic_response(error) + end + end + + @spec put(String.t(), Map.t(), Keyword.t()) :: {:error, :operation_failed} | {:error, :not_found} | {:error, :conflict} | {:error, any()} + def put(db, doc = %{"_id" => id, "_rev" => _}, params \\ []) do + {url, headers, options} = prepare_request([db, id], [{"content-type", "application/json"}], params) + case HTTPoison.put(url, Poison.encode!(doc), headers, options) do + {:ok, %HTTPoison.Response{status_code: ok, body: body}} when ok in [201, 202] -> + body = Poison.decode!(body) + if Map.get(body, "ok") do + {:ok, Map.get(body, "id"), Map.get(body, "rev")} + else + {:error, :operation_failed} + end + {:ok, %HTTPoison.Response{status_code: 209}} -> + {:error, :conflict} + error -> handle_generic_response(error) + end + end + + defp prepare_request(path, headers \\ [], params \\ [], options \\ []) do + config = Application.get_env(:lsg, :couch) + + base_url = Keyword.get(config, :url, "http://localhost:5984") + url = URI.merge(base_url, Path.join(path)) |> to_string() + + headers = headers ++ [{"accept", "application/json"}, {"user-agent", "beautte"}] + + params = Enum.map(params, fn({k, v}) -> {to_string(k), v} end) + client_options = Keyword.get(config, :client_options, []) + options = [params: params] ++ options ++ client_options + + user = Keyword.get(config, :user) + pass = Keyword.get(config, :pass) + hackney_options = Keyword.get(options, :hackney, []) + hackney_options = if user, do: [{:basic_auth, {user, pass}} | hackney_options], else: [] + options = [{:hackney, [:insecure | hackney_options]} | options] + + {url, headers, options} + end + + defp handle_generic_response(%HTTPoison.Response{status_code: code}), do: {:error, Plug.Conn.Status.reason_atom(code)} + defp handle_generic_response(%HTTPoison.Error{reason: reason}), do: {:error, reason} + end -- cgit v1.2.3