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
4cdd187b43
commit
62000562f0
1 changed files with 83 additions and 14 deletions
|
|
@ -42,15 +42,18 @@ defmodule Mv.Vereinfacht.Client do
|
||||||
end
|
end
|
||||||
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
|
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)
|
encoded_body = Jason.encode!(body)
|
||||||
|
|
||||||
case Req.post(url,
|
case Req.post(url, [body: encoded_body, headers: headers(api_key)] ++ req_http_options()) do
|
||||||
body: encoded_body,
|
|
||||||
headers: headers(api_key),
|
|
||||||
receive_timeout: 15_000
|
|
||||||
) do
|
|
||||||
{:ok, %{status: 201, body: resp_body}} ->
|
{:ok, %{status: 201, body: resp_body}} ->
|
||||||
case get_contact_id_from_response(resp_body) do
|
case get_contact_id_from_response(resp_body) do
|
||||||
nil -> {:error, {:invalid_response, resp_body}}
|
nil -> {:error, {:invalid_response, resp_body}}
|
||||||
|
|
@ -95,10 +98,12 @@ defmodule Mv.Vereinfacht.Client do
|
||||||
|> String.trim_trailing("/")
|
|> String.trim_trailing("/")
|
||||||
|> then(&"#{&1}/finance-contacts/#{contact_id}")
|
|> then(&"#{&1}/finance-contacts/#{contact_id}")
|
||||||
|
|
||||||
case Req.patch(url,
|
case Req.patch(
|
||||||
body: encoded_body,
|
url,
|
||||||
headers: headers(api_key),
|
[
|
||||||
receive_timeout: 15_000
|
body: encoded_body,
|
||||||
|
headers: headers(api_key)
|
||||||
|
] ++ req_http_options()
|
||||||
) do
|
) do
|
||||||
{:ok, %{status: 200, body: _resp_body}} ->
|
{:ok, %{status: 200, body: _resp_body}} ->
|
||||||
{:ok, contact_id}
|
{:ok, contact_id}
|
||||||
|
|
@ -112,6 +117,73 @@ defmodule Mv.Vereinfacht.Client do
|
||||||
end
|
end
|
||||||
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 """
|
@doc """
|
||||||
Fetches a single finance contact from Vereinfacht (GET /finance-contacts/:id).
|
Fetches a single finance contact from Vereinfacht (GET /finance-contacts/:id).
|
||||||
|
|
||||||
|
|
@ -130,10 +202,7 @@ defmodule Mv.Vereinfacht.Client do
|
||||||
|> String.trim_trailing("/")
|
|> String.trim_trailing("/")
|
||||||
|> then(&"#{&1}/finance-contacts/#{contact_id}")
|
|> then(&"#{&1}/finance-contacts/#{contact_id}")
|
||||||
|
|
||||||
case Req.get(url,
|
case Req.get(url, [headers: headers(api_key)] ++ req_http_options()) do
|
||||||
headers: headers(api_key),
|
|
||||||
receive_timeout: 15_000
|
|
||||||
) do
|
|
||||||
{:ok, %{status: 200, body: body}} when is_map(body) ->
|
{:ok, %{status: 200, body: body}} when is_map(body) ->
|
||||||
{:ok, body}
|
{:ok, body}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue