Current section
Files
Jump to
Current section
Files
lib/mpx.ex
defmodule Mpx do
@moduledoc """
Wrapper for the (Ministry Platform)[https://www.thinkministry.com/ministryplatform/] api.
For a complete reference to the MP API browse to `https://yourhost/ministryplatformapi/swagger`
"""
@dialyzer {:nowarn_function, from_config: 2}
@dialyzer {:nowarn_function, authenticate: 0}
@endpoint Application.get_env(:mpx, :mp_base_url, "")
@username Application.get_env(:mpx, :mp_username)
@password Application.get_env(:mpx, :mp_password)
@client_id Application.get_env(:mpx, :mp_client_id)
@client_secret Application.get_env(:mpx, :mp_client_secret)
@type authentication_opts :: [username: String.t, password: String.t, client_id: String.t, client_secret: String.t]
@type http :: {atom(), map() | list()} | {atom(), term()}
require Logger
@doc """
Authenticate with Ministry Platform.
Takes a keyword list of options in the shape:
```
[ username: "mp-username",
password: "mp-password",
client_id: "mp-client-id",
client_secret: "mp-client-secret" ]
```
If keyword options are not passed in, defaults to configuration parameters:
```
mpx, mp_username: "mp-username",
mp_password: System.get_env("MP_PASSWORD"),
mp_client_id: {:system, "MP_CLIENT_ID"},
mp_client_secret: "mp-client-secret"
```
Returns `{:ok, token}` where `token` can be used in subsequent calls to the API
that need to be authenticated or `{:error, reason}`
"""
@spec authenticate(authentication_opts | nil) :: {:ok, String.t} | {:error, String.t}
def authenticate(opts \\ []) do
Logger.debug("trying to authenticate")
response = HTTPoison.post(
"#{from_config(:mp_base_url, @endpoint)}/ministryplatform/oauth/token",
get_auth_body(opts),
%{"Content-type" => "application/x-www-form-urlencoded"}
)
case response do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
with {:ok, token} <- Poison.decode(body) do
{:ok, Map.get(token, "access_token")}
else
{:error, {:invalid, line, char}} -> {:error, "unable to decode token from " <> body <> " because of " <> char <> " at line " <> line}
{:error, err} -> {:error, "unable to decode token from " <> body <> " because of " <> err}
end
{:ok, %HTTPoison.Response{status_code: code, body: body}} ->
Logger.debug(inspect(body))
{:error, body}
{:error, %HTTPoison.Error{reason: reason}} ->
Logger.debug(inspect(reason))
{:error, reason}
_ ->
{:error, :unknown}
end
end
@doc """
Wrapper for the Ministry Platform GET Api call.
Takes optional keyword list of options:
`[ "$select": "The_columns_to_select",
"$filter": "The filter to apply",
etc...
]`
See swagger for a complete list of parameters that the MP rest api takes.
Returns an `HTTPoison.Response` or `HTTPoison.Error`
"""
@spec get(binary(), binary(), keyword(binary)) :: http
def get(path, auth_token, opts \\ []) do
HTTPoison.get(
"#{from_config(:mp_base_url, @endpoint)}/ministryplatformapi/#{path}#{stringify_url_options(opts)}",
%{"Authorization" => "Bearer #{auth_token}"})
|> map_response
end
@doc """
Wrapper for the Ministry Platform POST Api call
Takes optional keyword list of options:
`[ "$select": "The_columns_to_select",
"$filter": "The filter to apply",
etc...
]`
see swagger for a complete list of parameters that the MP rest api takes.
Returns an `HTTPoison.Response` or `HTTPoison.Error`
"""
@spec post(binary(), binary(), map() | list(), keyword(binary())) :: http
def post(path, auth_token, data, opts \\ []) do
case Poison.encode(data) do
{:ok, encoded_data} -> HTTPoison.post(
"#{from_config(:mp_base_url, @endpoint)}/ministryplatformapi/#{path}#{stringify_url_options(opts)}",
encoded_data,
%{"Authorization" => "Bearer #{auth_token}"})
|> map_response
err -> err
end
end
@doc """
Wrapper for the Ministry Platform PUT Api call
Takes optional keyword list of options:
`[ "$select": "The_columns_to_select",
"$filter": "The filter to apply",
etc...
]`
see the swagger documentation for a
complete list of parameters that the MP rest api takes.
Returns an `HTTPoison.Response` or `HTTPoison.Error`
"""
@spec put(binary(), binary(), map() | list(), keyword(binary())) :: http
def put(path, auth_token, data, opts \\ []) do
case Poison.encode(data) do
{:ok, encoded_data} -> HTTPoison.put(
"#{from_config(:mp_base_url, @endpoint)}/ministryplatformapi/#{path}#{stringify_url_options(opts)}",
encoded_data,
%{"Authorization" => "Bearer #{auth_token}"})
|> map_response
err -> err
end
end
@doc """
Wrapper for the Ministry Platform DELETE Api call
Takes optional keyword list of options:
`[ "$select": "The_columns_to_select",
"$filter": "The filter to apply",
etc...
]`
see swagger for a complete list of parameters that the MP rest api takes
Returns an `HTTPoison.Response` or `HTTPoison.Error`
"""
@spec delete(binary(), binary(), Mpx.Tables.delete_options) :: http
def delete(path, auth_token, opts \\ []) do
HTTPoison.delete(
"#{from_config(:mp_base_url, @endpoint)}/ministryplatformapi/#{path}#{stringify_url_options(opts)}",
%{"Authorization" => "Bearer #{auth_token}"})
|> map_response
end
@doc false
@spec stringify_url_options(keyword() | []) :: binary()
defp stringify_url_options([]), do: ""
defp stringify_url_options(opts) do
options = opts
|> unique_keys
|> Enum.reduce([], fn(key, acc) ->
vals = opts
|> Keyword.get_values(key)
|> Enum.map(fn(x) -> "#{key}=#{x}" end)
acc ++ vals
end)
|> Enum.join("&")
"?#{options}"
end
@spec unique_keys(Keyword.t) :: list()
defp unique_keys(keyword_list) do
keyword_list
|> Enum.uniq_by(fn {k, _} -> k end)
|> Keyword.keys
end
@doc false
@spec map_response({:ok, HTTPoison.Response.t} | {:error, HTTPoison.Error}) :: {:ok, map() | list()} | {:error, binary()}
defp map_response({:ok, %HTTPoison.Response{body: body, status_code: 200}}), do: Poison.decode(body)
defp map_response({:ok, %HTTPoison.Response{body: body, status_code: bad}}), do: {:error, Poison.decode!(body)}
defp map_response({:error, %HTTPoison.Error{id: id, reason: reason}}), do: {:error, Poison.decode!(reason)}
@doc false
@spec get_auth_body(authentication_opts | nil) :: String.t
defp get_auth_body(username: username, password: password, client_id: client_id, client_secret: client_secret) do
["username=#{URI.encode(username)}",
"&password=#{password}",
"&client_id=#{client_id}",
"&client_secret=#{client_secret}",
"&grant_type=password"] |> Enum.join
end
defp get_auth_body([]) do
get_auth_body(username: from_config(:mp_username, @username),
password: from_config(:mp_password, @password),
client_id: from_config(:mp_client_id, @client_id),
client_secret: from_config(:mp_client_secret, @client_secret))
end
defp get_auth_body(opts), do: raise "Wrong arguments passed to authenticate. Please setup your configuration correctly or pass a keyword list in the with username, password, client_id and client_secret"
@doc false
@spec from_config(atom(), term() | nil) :: no_return | String.t
defp from_config(config_param, nil), do: raise "#{config_param} is a required configuration parameter when calling authenticate() without parameters"
defp from_config(_config_param, binary) when is_binary(binary), do: binary
defp from_config(_config_param, integer) when is_integer(integer), do: Integer.to_string(integer)
defp from_config(config_param, {:system, env_var}), do: from_config(config_param, System.get_env(env_var))
defp from_config(config_param, bad_val), do: from_config(config_param, nil)
end