Vereinfacht client: find by email in response, no retries in test
API does not allow filter[email]; fetch list and match client-side. Disable Req retries in test for fast failure and less log noise.
This commit is contained in:
parent
c33199465c
commit
bc2d91f9e7
1 changed files with 83 additions and 14 deletions
|
|
@ -42,15 +42,18 @@ defmodule Mv.Vereinfacht.Client do
|
|||
end
|
||||
end
|
||||
|
||||
@sync_timeout_ms 5_000
|
||||
|
||||
# In test, skip retries so sync fails fast when no API is running (avoids log spam and long waits).
|
||||
defp req_http_options do
|
||||
opts = [receive_timeout: @sync_timeout_ms]
|
||||
if Mix.env() == :test, do: [retry: false] ++ opts, else: opts
|
||||
end
|
||||
|
||||
defp post_and_parse_contact(url, body, api_key) do
|
||||
# Req expects body to be iodata (e.g. string); a raw map causes ArgumentError.
|
||||
encoded_body = Jason.encode!(body)
|
||||
|
||||
case Req.post(url,
|
||||
body: encoded_body,
|
||||
headers: headers(api_key),
|
||||
receive_timeout: 15_000
|
||||
) do
|
||||
case Req.post(url, [body: encoded_body, headers: headers(api_key)] ++ req_http_options()) do
|
||||
{:ok, %{status: 201, body: resp_body}} ->
|
||||
case get_contact_id_from_response(resp_body) do
|
||||
nil -> {:error, {:invalid_response, resp_body}}
|
||||
|
|
@ -95,10 +98,12 @@ defmodule Mv.Vereinfacht.Client do
|
|||
|> String.trim_trailing("/")
|
||||
|> then(&"#{&1}/finance-contacts/#{contact_id}")
|
||||
|
||||
case Req.patch(url,
|
||||
body: encoded_body,
|
||||
headers: headers(api_key),
|
||||
receive_timeout: 15_000
|
||||
case Req.patch(
|
||||
url,
|
||||
[
|
||||
body: encoded_body,
|
||||
headers: headers(api_key)
|
||||
] ++ req_http_options()
|
||||
) do
|
||||
{:ok, %{status: 200, body: _resp_body}} ->
|
||||
{:ok, contact_id}
|
||||
|
|
@ -112,6 +117,73 @@ defmodule Mv.Vereinfacht.Client do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a finance contact by email (GET /finance-contacts, then match in response).
|
||||
|
||||
The Vereinfacht API does not allow filter by email on this endpoint, so we
|
||||
fetch the first page and find the contact client-side. Returns {:ok, contact_id}
|
||||
if a contact with that email exists, {:error, :not_found} if none, or
|
||||
{:error, reason} on API/network failure. Used before create for idempotency.
|
||||
"""
|
||||
@spec find_contact_by_email(String.t()) :: {:ok, String.t()} | {:error, :not_found | term()}
|
||||
def find_contact_by_email(email) when is_binary(email) do
|
||||
if is_nil(base_url()) or is_nil(api_key()) or is_nil(club_id()) do
|
||||
{:error, :not_configured}
|
||||
else
|
||||
do_find_contact_by_email(email)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_find_contact_by_email(email) do
|
||||
url =
|
||||
base_url()
|
||||
|> String.trim_trailing("/")
|
||||
|> then(&"#{&1}/finance-contacts")
|
||||
|
||||
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)
|
||||
|
||||
{:ok, %{status: status, body: body}} ->
|
||||
{:error, {:http, status, extract_error_message(body)}}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, {:request_failed, reason}}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_find_by_email_response(body, email) do
|
||||
normalized = String.trim(email) |> String.downcase()
|
||||
|
||||
case find_contact_id_by_email_in_list(body, normalized) do
|
||||
nil -> {:error, :not_found}
|
||||
id -> {:ok, id}
|
||||
end
|
||||
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}} when is_binary(att_email) ->
|
||||
if att_email |> String.trim() |> String.downcase() == normalized do
|
||||
normalize_contact_id(id)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
%{"id" => _id, "attributes" => _} ->
|
||||
nil
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end)
|
||||
end
|
||||
|
||||
defp find_contact_id_by_email_in_list(_, _), do: nil
|
||||
|
||||
defp normalize_contact_id(id) when is_binary(id), do: id
|
||||
defp normalize_contact_id(id) when is_integer(id), do: to_string(id)
|
||||
defp normalize_contact_id(_), do: nil
|
||||
|
||||
@doc """
|
||||
Fetches a single finance contact from Vereinfacht (GET /finance-contacts/:id).
|
||||
|
||||
|
|
@ -130,10 +202,7 @@ defmodule Mv.Vereinfacht.Client do
|
|||
|> String.trim_trailing("/")
|
||||
|> then(&"#{&1}/finance-contacts/#{contact_id}")
|
||||
|
||||
case Req.get(url,
|
||||
headers: headers(api_key),
|
||||
receive_timeout: 15_000
|
||||
) do
|
||||
case Req.get(url, [headers: headers(api_key)] ++ req_http_options()) do
|
||||
{:ok, %{status: 200, body: body}} when is_map(body) ->
|
||||
{:ok, body}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue