defmodule Meta.Odoo do use GenServer require Logger @common_endpoint "/xmlrpc/2/common" @object_endpoint "/xmlrpc/2/object" # Odoo 14.0 API is documented at https://www.odoo.com/documentation/14.0/webservices/odoo.html defp get_odoo_config(key) do Application.get_env(:meta, :odoo, []) |> Keyword.get(key) end defp post!(call, endpoint) do url = get_odoo_config(:server) <> endpoint opts = [proxy_auth: {get_odoo_config(:user), get_odoo_config(:secret)}] headers = [] body = call |> XMLRPC.encode! response = HTTPoison.post!(url, body, headers, opts).body |> XMLRPC.decode! case response do %{fault_code: _, fault_string: err} -> {:error, err} %{param: result} -> {:ok, result} end end defp authenticate do auth_params = [ get_odoo_config(:database), get_odoo_config(:user), get_odoo_config(:secret), nil ] try do auth_response = %XMLRPC.MethodCall{method_name: "authenticate", params: auth_params} |> post!(@common_endpoint) case auth_response do {:ok, false} -> {:error, "Could not authenticate against Odoo."} {:ok, uid} -> {:ok, uid} {:error, err} -> {:error, err} end rescue e -> {:error, e} end end defp check_odoo_config(key) do case get_odoo_config(key) do nil -> Logger.warn("Odoo #{key} configuration is missing.") false _value -> true end end def start_link(_) do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) end def init(:ok) do # Make sure Odoo configuration is defined. conf_ok? = for key <- [:server, :database, :user, :secret], reduce: true do acc -> acc && check_odoo_config(key) end if conf_ok? do case authenticate() do {:ok, uid} -> # Print Odoo version on console (not quite useful...). version_call = %XMLRPC.MethodCall{method_name: "version", params: []} |> post!(@common_endpoint) {:ok, %{"server_version" => version}} = version_call Logger.info("Successfuly authenticated against Odoo #{version} at #{get_odoo_config(:server)}") {:ok, uid} {:error, err} -> Logger.warning("Failed to authenticate against Odoo: #{inspect(err)}") # We do not fail here since we do not want to hinder the application's # main supervisor if Odoo is down. {:ok, nil} end else Logger.warning("Failed to authenticate against Odoo: missing configuration!") {:ok, nil} end end def handle_call({method, params}, _from, uid) do case uid do # uid is nil if start_link's init/1 call failed. # ... we try to authenticate once again! nil -> case init(:ok) do {:ok, new_uid} -> handle_call({method, params}, nil, new_uid) err -> {:reply, err, nil} end _ -> call = %XMLRPC.MethodCall{ method_name: method, params: [get_odoo_config(:database), uid, get_odoo_config(:secret) | params] } result = call |> post!(@object_endpoint) {:reply, result, uid} end end def query(params), do: execute_kw(params) defp execute_kw(params) do GenServer.call(__MODULE__, {"execute_kw", params}) end end