Can you show us the final code snippet? Sharing the solution could help someone else in the future.
Yep
The task is to find duplicate entries and delete everything and spare the most optimal one
defmodule Mix.Tasks.App.CleanUpDuplicateAccounts do
use Mix.Task
import Ecto.Query, only: [where: 3, from: 2]
alias App.Data.{....
}
require Logger
def run(_args) do
Logger.info("Starting task to clean up duplicate accounts...")
{:ok, _} = Application.ensure_all_started(:data)
query =
from(u in User,
where:
fragment(
"exists (
select 1
from users inner
where inner.id != ?
and lower(inner.email) = lower(?)
)",
u.id,
u.email
),
order_by: u.email
)
query
|> Repo.all()
|> Enum.group_by(&String.downcase(&1.email))
|> Enum.each(fn {_k, duplicates} ->
comparing_statuses(duplicates)
end)
end
defp get_tasks_for_users(id) do
# Logic return tasks
end
defp check_and_remove_account(user) do
if !user.is_email_verified ||
is_nil(user.encrypted_password) do
tasks = get_tasks_for_user(user.id)
case tasks do
[] ->
Logger.info("Deleting user: id #{user.id} with email #{user.email}")
Users.delete(user)
_ ->
Logger.info("Unable to delete - user: id #{user.id} has active tasks")
end
end
end
defp mass_deletion_check(duplicates) do
are_all_duplicates_not_verified =
Enum.all?(duplicates, &(!&1.is_email_verified || is_nil(&1.encrypted_password)))
if are_all_duplicates_not_verified do
[_ | duplicates_to_delete] = duplicates
Enum.each(
duplicates_to_delete,
fn index ->
check_and_remove_account(index)
end
)
else
Enum.each(duplicates, fn index ->
if !index.is_email_verified or is_nil(index.encrypted_password) do
check_and_remove_account(index)
else
Logger.info(
"Not deleting - user: id #{index.id} has email is verified or password is set"
)
end
end)
end
end
defp get_handle_for_user(id) do
# logic to return handles
end
# If handles are the same then start mass_deletion_check for the duplicates
# If handles are different then delete email from accounts except one
defp handle_or_email_deletion_check(duplicates) do
duplicates_with_handle =
duplicates
|> Enum.reduce([], fn user, acc ->
acc ++ [Map.put(user, :handle, get_handle_for_user(user.id))]
end)
handle_group = Enum.group_by(duplicates_with_handle, & &1.handle)
if Enum.count(handle_group) < 2 do
mass_deletion_check(duplicates)
else
[_ | handle_group_to_delete_email] = Enum.to_list(handle_group)
Enum.each(handle_group_to_delete_email, fn {_k, duplicates} ->
remove_email_except_one(duplicates)
end)
end
end
defp remove_email_except_one(duplicates) do
Enum.each(duplicates, fn user ->
Users.update(user, %{email: nil})
end)
end
defp count_with_status(duplicates, status) when not is_list(status) do
count_with_status(duplicates, [status])
end
defp count_with_status(duplicates, status) do
Enum.count(
duplicates,
&(&1.user_status_id in UserStatuses.get_id_by_code(status))
)
end
defp accumulating(duplicates, status) do
duplicates
|> Enum.reduce([], fn user, acc ->
if user.user_status_id in UserStatuses.get_id_by_code(status) do
acc ++ [user]
else
check_and_remove_account(user)
acc
end
end)
end
defp accumulating_accounts_with_required_status(duplicates, status)
when status == "approval_requested" do
duplicates_account_with_approval_requested = accumulating(duplicates, [status])
delete_all_and_update_first_one(duplicates_account_with_approval_requested)
end
defp accumulating_accounts_with_required_status(duplicates, status) when not is_list(status) do
duplicates_to_delete = accumulating(duplicates, [status])
mass_deletion_check(duplicates_to_delete)
end
defp accumulating_accounts_with_required_status(duplicates, status) do
duplicates_to_delete = accumulating(duplicates, status)
mass_deletion_check(duplicates_to_delete)
end
defp iterate_and_remove(duplicates, status)
when not is_list(status) do
iterate_and_remove(duplicates, [status])
end
defp iterate_and_remove(duplicates, status) do
Enum.each(duplicates, fn user ->
if user.user_status_id not in UserStatuses.get_id_by_code(status) do
check_and_remove_account(user)
end
end)
end
defp iterate_and_update(duplicates, status) do
Enum.each(duplicates, fn user ->
if user.user_status_id ==
UserStatuses.get_id_by_code(status) do
Users.update(user, %{email: nil})
end
end)
end
defp iterate_and_update(duplicates, status_one, status_two) do
Enum.each(duplicates, fn user ->
if user.user_status_id !==
UserStatuses.get_id_by_code(status_one) do
check_and_remove_account(user)
else
Users.update(user, %{
user_status_id: UserStatuses.get_id_by_code(status_two)
})
end
end)
end
defp deleting_with_approached_and_approved_case(duplicates) do
duplicates_approved_and_approached =
Enum.reduce(duplicates, [], fn user, acc ->
if user.user_status_id !== UserStatuses.get_id_by_code("approved") and
user.user_status_id !==
UserStatuses.get_id_by_code("approached") do
check_and_remove_account(user)
acc
else
acc ++ [user]
end
end)
handle_from_approved =
get_handle_for_user(Enum.at(duplicates_approved_and_approached, 0).id)
handle_from_approached =
get_handle_for_user(Enum.at(duplicates_approved_and_approached, 1).id)
case {handle_from_approved, handle_from_approached} do
{handle_from_approved, handle_from_approached}
when handle_from_approved == handle_from_approached and not is_nil(handle_from_approved) and
not is_nil(handle_from_approached) ->
iterate_and_remove(
duplicates_approved_and_approached,
"approached"
)
_ ->
iterate_and_update(
duplicates_approved_and_approached,
"approached"
)
end
end
defp delete_all_and_update_first_one(duplicates) do
[user_to_update | duplicates_to_delete] = duplicates
Enum.each(duplicates_to_delete, fn user -> check_and_remove_account(user) end)
Users.update(user_to_update, %{
user_status_id: UserStatuses.get_id_by_code("approved")
})
end
defp count_duplicates_statuses(duplicates) do
approved_count = count_with_status(duplicates, "approved")
invalid_count =
count_with_status(duplicates, [
"invalid",
"under_investigation",
"suspended"
])
approached_count = count_with_status(duplicates, "approached")
rejected_count = count_with_status(duplicates, "rejected")
external_count = count_with_status(duplicates, "external")
approval_requested_count = count_with_status(duplicates, "approval_requested")
{approved_count, invalid_count, approached_count, rejected_count, external_count,
approval_requested_count}
end
defp comparing_statuses(duplicates) do
statuses = count_duplicates_statuses(duplicates)
handle_statuses(statuses, duplicates)
end
defp handle_statuses({1, _, 0, _, _, _}, duplicates) do
iterate_and_remove(duplicates, "approved")
end
defp handle_statuses({1, _, 1, _, _, _}, duplicates) do
deleting_with_approached_and_approved_case(duplicates)
end
defp handle_statuses(
{approved_count, _, 0, _, _, _},
duplicates
)
when approved_count >= 2 do
accumulating_accounts_with_required_status(duplicates, "approved")
end
defp handle_statuses({0, 1, _, _, _, _}, duplicates) do
iterate_and_remove(duplicates, [
"invalid",
"under_investigation",
"suspended"
])
end
defp handle_statuses({0, invalid_count, _, _, _, _}, duplicates) when invalid_count >= 2 do
accumulating_accounts_with_required_status(duplicates, [
"invalid",
"under_investigation",
"suspended"
])
end
defp handle_statuses({0, 0, 1, _, _, 0}, duplicates) do
iterate_and_remove(duplicates, "approached")
end
defp handle_statuses({0, 0, 1, _, _, 1}, duplicates) do
iterate_and_update(
duplicates,
"approval_requested",
"approved"
)
end
defp handle_statuses({0, 0, approached_count, _, _, approval_requested_count}, duplicates)
when approached_count >= 2 and approval_requested_count >= 2 do
accumulating_accounts_with_required_status(duplicates, "approval_requested")
end
defp handle_statuses({0, 0, approached_count, _, _, 0}, duplicates)
when approached_count >= 2 do
iterate_and_remove(duplicates, "approached")
handle_or_email_deletion_check(duplicates)
end
defp handle_statuses({0, 0, 0, 1, _, _}, duplicates) do
iterate_and_remove(duplicates, "rejected")
end
defp handle_statuses({0, 0, 0, rejected_count, _, _}, duplicates) when rejected_count >= 2 do
accumulating_accounts_with_required_status(duplicates, "rejected")
end
defp handle_statuses({0, 0, 0, 0, 1, _}, duplicates) do
iterate_and_remove(duplicates, "external")
end
defp handle_statuses({0, 0, 0, 0, external_count, _}, duplicates) when external_count >= 2 do
accumulating_accounts_with_required_status(duplicates, "external")
end
defp handle_statuses({0, 0, 0, 0, 0, approval_requested_count}, duplicates)
when approval_requested_count >= 2 do
mass_deletion_check(duplicates)
end
end
2 Likes