Skip to content

Update for newsletter compatibility #163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ services:
- "6379:6379"

mysql:
image: mysql:5.7.24
image: mysql:5.7
environment:
- MYSQL_DATABASE=recognizer_test
- MYSQL_DATABASE=recognizer_dev
- MYSQL_ROOT_PASSWORD=recognizer
command:
- --character-set-server=utf8
- --collation-server=utf8_general_ci
# - --innodb_force_recovery=4 # 1~6 중 선택 (4는 일반적인 복구 시도용)
ports:
- "3306:3306"
volumes:
- ./mysql_data:/var/lib/mysql
15 changes: 14 additions & 1 deletion lib/recognizer/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,20 @@ defmodule Recognizer.Accounts do
end

defp maybe_send_newsletter_after_registration({:ok, user} = previous_response, %{"newsletter" => "true"}) do
Recognizer.Hal.update_newsletter(user)
# Process asynchronously to avoid blocking the account creation if newsletter registration fails
Task.start(fn ->
try do
require Logger
result = Recognizer.Hal.update_newsletter(user)
Logger.info("Newsletter registration completed for user #{user.id}: #{inspect(result)}")
catch
kind, reason ->
require Logger
Logger.error("Newsletter registration failed for user #{user.id}: #{inspect(kind)}, #{inspect(reason)}")
Logger.error(Exception.format_stacktrace(__STACKTRACE__))
end
end)

previous_response
end

Expand Down
75 changes: 54 additions & 21 deletions lib/recognizer/hal.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ defmodule Recognizer.Hal do
with {:ok, validated_user} <- validate_user_data(user),
{:ok, interests_url} <- build_url("/accounts/newsletter/interests"),
{:ok, interests_response_body} <-
fetch_data(interests_url, "newsletter interests", Map.get(validated_user, :email)),
fetch_data_with_retry(interests_url, "newsletter interests", Map.get(validated_user, :email)),
{:ok, decoded_interests} <-
decode_json(interests_response_body, "newsletter interests", Map.get(validated_user, :email)),
groups <- calculate_interest_groups(decoded_interests, validated_user),
{:ok, status_url} <- build_url("/accounts/newsletter?email_address=#{Map.get(validated_user, :email)}"),
{:ok, status_response_body} <- fetch_data(status_url, "newsletter status", Map.get(validated_user, :email)),
{:ok, status_response_body} <-
fetch_data_with_retry(status_url, "newsletter status", Map.get(validated_user, :email)),
{:ok, decoded_status} <-
decode_json(status_response_body, "newsletter status", Map.get(validated_user, :email)),
:update_allowed <- check_and_log_newsletter_status(decoded_status, Map.get(validated_user, :email)) do
Expand All @@ -28,8 +29,8 @@ defmodule Recognizer.Hal do
# Handle any error from the with statement
{:error, reason} ->
Logger.error("Newsletter update failed for #{Map.get(user, :email, "unknown user")}: #{inspect(reason)}")
# Or return the specific error reason
:error
# Return the specific error reason
{:error, reason}

:update_not_allowed ->
# Logged in check_and_log_newsletter_status, so just return :ok or a specific atom
Expand All @@ -38,7 +39,7 @@ defmodule Recognizer.Hal do
_other_error ->
# Catch-all for unexpected failures in `with`
Logger.error("Unexpected error during newsletter update for #{Map.get(user, :email, "unknown user")}")
:error
{:error, :unexpected_error}
end
end

Expand All @@ -55,6 +56,22 @@ defmodule Recognizer.Hal do
end
end

# Fetch data with retry mechanism
defp fetch_data_with_retry(url, context_msg, email_for_log, retry_count \\ 3, retry_delay \\ 1000) do
case fetch_data(url, context_msg, email_for_log) do
{:ok, body} ->
{:ok, body}

{:error, _reason} when retry_count > 0 ->
Logger.warn("Retrying fetch for #{context_msg}, attempts left: #{retry_count - 1}")
Process.sleep(retry_delay)
fetch_data_with_retry(url, context_msg, email_for_log, retry_count - 1, retry_delay * 2)

error ->
error
end
end

# Fetch data from HAL API
defp fetch_data(url, context_msg, email_for_log) do
case HTTPoison.get(url, authorization_headers()) do
Expand Down Expand Up @@ -123,22 +140,7 @@ defmodule Recognizer.Hal do
with {:ok, email_address} <- validate_email_present(user),
payload <- build_payload(email_address, user, groups),
{:ok, post_url} <- build_url("/accounts/newsletter") do
case HTTPoison.post(post_url, payload, [{"content-type", "application/json"}] ++ authorization_headers()) do
{:ok, %HTTPoison.Response{status_code: 200}} ->
Logger.info("Newsletter updated successfully for #{email_address}")
:ok

{:ok, %HTTPoison.Response{status_code: status_code, body: error_body}} ->
Logger.error(
"Failed to update newsletter for #{email_address}. Status: #{status_code}, Body: #{inspect(error_body)}"
)

{:error, {:http_post_error, status_code}}

{:error, %HTTPoison.Error{reason: reason}} ->
Logger.error("HTTP error while posting newsletter update for #{email_address}: #{inspect(reason)}")
{:error, {:http_client_post_error, reason}}
end
post_newsletter_with_retry(post_url, payload, email_address, 3)
else
# This else block will catch errors from validate_email_present or build_url
{:error, reason} ->
Expand All @@ -152,6 +154,37 @@ defmodule Recognizer.Hal do
end
end

# Newsletter POST function with retry mechanism
defp post_newsletter_with_retry(post_url, payload, email_address, retry_count, retry_delay \\ 1000) do
case HTTPoison.post(post_url, payload, [{"content-type", "application/json"}] ++ authorization_headers()) do
{:ok, %HTTPoison.Response{status_code: 200}} ->
Logger.info("Newsletter updated successfully for #{email_address}")
:ok

{:ok, %HTTPoison.Response{status_code: status_code, body: _error_body}}
when retry_count > 0 and status_code >= 500 ->
Logger.warn("Newsletter update failed with status #{status_code}, retrying. Attempts left: #{retry_count - 1}")
Process.sleep(retry_delay)
post_newsletter_with_retry(post_url, payload, email_address, retry_count - 1, retry_delay * 2)

{:ok, %HTTPoison.Response{status_code: status_code, body: error_body}} ->
Logger.error(
"Failed to update newsletter for #{email_address}. Status: #{status_code}, Body: #{inspect(error_body)}"
)

{:error, {:http_post_error, status_code}}

{:error, %HTTPoison.Error{reason: _reason}} when retry_count > 0 ->
Logger.warn("HTTP error while posting newsletter update, retrying. Attempts left: #{retry_count - 1}")
Process.sleep(retry_delay)
post_newsletter_with_retry(post_url, payload, email_address, retry_count - 1, retry_delay * 2)

{:error, %HTTPoison.Error{reason: reason}} ->
Logger.error("HTTP error while posting newsletter update for #{email_address}: #{inspect(reason)}")
{:error, {:http_client_post_error, reason}}
end
end

defp validate_email_present(user) do
case Map.get(user, :email) do
nil -> {:error, :missing_email_for_newsletter_update}
Expand Down
13 changes: 11 additions & 2 deletions lib/recognizer/notifications/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,17 @@ defmodule Recognizer.Notifications.Account do

@decorate span(service: :bullhorn, type: :function)
defp send_message(resource) do
Bottle.publish(resource, source: "recognizer", request_id: Bottle.RequestId.write(:http))
{:ok, resource}
request_id = "recognizer-#{:os.system_time(:millisecond)}-#{System.unique_integer([:positive])}"

try do
Bottle.publish(resource, source: "recognizer", request_id: request_id)
{:ok, resource}
rescue
e ->
require Logger
Logger.error("Failed to publish message to bullhorn: #{inspect(e)}")
{:ok, resource}
end
end
else
defp send_message(resource) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ defmodule RecognizerWeb.Accounts.UserOAuthController do
end

defp provider_params(%{provider: :google, info: info}) do
Map.take(info, [:email, :first_name, :last_name])
%{
email: info.email,
first_name: info.first_name || "Unknown",
last_name: info.last_name || "User"
}
end

# Github doesn't return `first_name` and `last_name` like other providers,
Expand All @@ -109,11 +113,21 @@ defmodule RecognizerWeb.Accounts.UserOAuthController do
}
end

defp split_name(nil), do: ["", ""]
defp split_name(nil), do: ["Unknown", "User"]

defp split_name(name) when is_binary(name) do
case String.split(name, " ", parts: 2) do
[first_name] ->
# Only one word, use it as first name and provide default last name
[first_name, "User"]

[first_name, last_name] ->
# Two or more words, split into first and rest as last name
[first_name, last_name]

defp split_name(name) do
with [first_name] <- String.split(name, " ", parts: 2) do
[first_name, ""]
[] ->
# Empty string case
["Unknown", "User"]
end
end
end