Vereinfacht accounting software API closes #431 #432
1 changed files with 37 additions and 24 deletions
|
|
@ -134,15 +134,25 @@ defmodule Mv.Vereinfacht.Client do
|
|||
end
|
||||
end
|
||||
|
||||
@find_contact_page_size 100
|
||||
@find_contact_max_pages 100
|
||||
|
||||
defp do_find_contact_by_email(email) do
|
||||
url =
|
||||
base_url()
|
||||
|> String.trim_trailing("/")
|
||||
|> then(&"#{&1}/finance-contacts")
|
||||
normalized = String.trim(email) |> String.downcase()
|
||||
do_find_contact_by_email_page(1, normalized)
|
||||
end
|
||||
|
||||
defp do_find_contact_by_email_page(page, _normalized) when page > @find_contact_max_pages do
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
defp do_find_contact_by_email_page(page, normalized) do
|
||||
base = base_url() |> String.trim_trailing("/") |> then(&"#{&1}/finance-contacts")
|
||||
url = base <> "?page[size]=#{@find_contact_page_size}&page[number]=#{page}"
|
||||
|
||||
case Req.get(url, [headers: headers(api_key())] ++ req_http_options()) do
|
||||
{:ok, %{status: 200, body: body}} when is_map(body) ->
|
||||
parse_find_by_email_response(body, email)
|
||||
handle_find_contact_page_response(body, page, normalized)
|
||||
|
||||
{:ok, %{status: status, body: body}} ->
|
||||
{:error, {:http, status, extract_error_message(body)}}
|
||||
|
|
@ -152,15 +162,21 @@ defmodule Mv.Vereinfacht.Client do
|
|||
end
|
||||
end
|
||||
|
||||
defp parse_find_by_email_response(body, email) do
|
||||
normalized = String.trim(email) |> String.downcase()
|
||||
|
||||
defp handle_find_contact_page_response(body, page, normalized) do
|
||||
case find_contact_id_by_email_in_list(body, normalized) do
|
||||
nil -> {:error, :not_found}
|
||||
id -> {:ok, id}
|
||||
id when is_binary(id) -> {:ok, id}
|
||||
nil -> maybe_find_contact_next_page(body, page, normalized)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_find_contact_next_page(body, page, normalized) do
|
||||
data = Map.get(body, "data") || []
|
||||
|
||||
if length(data) < @find_contact_page_size,
|
||||
do: {:error, :not_found},
|
||||
else: do_find_contact_by_email_page(page + 1, normalized)
|
||||
end
|
||||
|
||||
defp find_contact_id_by_email_in_list(%{"data" => list}, normalized) when is_list(list) do
|
||||
Enum.find_value(list, fn
|
||||
%{"id" => id, "attributes" => %{"email" => att_email, "isExternal" => true}}
|
||||
|
|
@ -249,31 +265,28 @@ defmodule Mv.Vereinfacht.Client do
|
|||
base <> sep <> "include=" <> URI.encode(value, &URI.char_unreserved?/1)
|
||||
end
|
||||
|
||||
# Allowlist of receipt attribute keys we expose (avoids String.to_atom on arbitrary API input / DoS).
|
||||
@receipt_attr_allowlist ~w[amount bookingDate createdAt receiptType referenceNumber status updatedAt]a
|
||||
|
||||
defp extract_receipts_from_response(%{"included" => included}) when is_list(included) do
|
||||
included
|
||||
|> Enum.filter(&match?(%{"type" => "receipts"}, &1))
|
||||
|> Enum.map(fn %{"id" => id, "attributes" => attrs} = r ->
|
||||
Map.merge(%{id: id, type: r["type"]}, string_keys_to_atoms(attrs || %{}))
|
||||
Map.merge(%{id: id, type: r["type"]}, receipt_attrs_allowlist(attrs || %{}))
|
||||
end)
|
||||
end
|
||||
|
||||
defp extract_receipts_from_response(_), do: []
|
||||
|
||||
defp string_keys_to_atoms(map) when is_map(map) do
|
||||
Map.new(map, fn {k, v} -> {to_atom_key(k), v} end)
|
||||
defp receipt_attrs_allowlist(attrs) when is_map(attrs) do
|
||||
Map.new(@receipt_attr_allowlist, fn key ->
|
||||
str_key = to_string(key)
|
||||
{key, Map.get(attrs, str_key)}
|
||||
end)
|
||||
|> Enum.reject(fn {_k, v} -> is_nil(v) end)
|
||||
|> Map.new()
|
||||
end
|
||||
|
||||
defp to_atom_key(k) when is_binary(k) do
|
||||
try do
|
||||
String.to_existing_atom(k)
|
||||
rescue
|
||||
ArgumentError -> String.to_atom(k)
|
||||
end
|
||||
end
|
||||
|
||||
defp to_atom_key(k) when is_atom(k), do: k
|
||||
defp to_atom_key(k), do: to_atom_key(to_string(k))
|
||||
|
||||
defp base_url, do: Mv.Config.vereinfacht_api_url()
|
||||
defp api_key, do: Mv.Config.vereinfacht_api_key()
|
||||
defp club_id, do: Mv.Config.vereinfacht_club_id()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue