Add soft delete user account

This commit is contained in:
Alex Lion
2024-12-28 13:13:55 -05:00
parent b695226fec
commit f3a36163b6
10 changed files with 80 additions and 30 deletions

View File

@@ -16,6 +16,7 @@
- Improve design and UX for interactions and presentation settings in the manager view - Improve design and UX for interactions and presentation settings in the manager view
- Add pagination for events on the dashboard - Add pagination for events on the dashboard
- Fix STMP adapter to work with secure connection - Fix STMP adapter to work with secure connection
- Add soft delete for user accounts
## v2.2.0 ## v2.2.0

View File

@@ -38,7 +38,9 @@ defmodule Claper.Accounts do
""" """
def get_user_by_email(email) when is_binary(email) do def get_user_by_email(email) when is_binary(email) do
Repo.get_by(User, email: email) User
|> where([u], is_nil(u.deleted_at))
|> Repo.get_by(email: email)
end end
@doc """ @doc """
@@ -79,8 +81,8 @@ defmodule Claper.Accounts do
""" """
def get_user_by_email_and_password(email, password) def get_user_by_email_and_password(email, password)
when is_binary(email) and is_binary(password) do when is_binary(email) and is_binary(password) do
user = Repo.get_by(User, email: email) user = User |> where([u], u.email == ^email and is_nil(u.deleted_at)) |> Repo.one()
if User.valid_password?(user, password), do: user if user && User.valid_password?(user, password), do: user
end end
@doc """ @doc """
@@ -99,6 +101,37 @@ defmodule Claper.Accounts do
""" """
def get_user!(id), do: Repo.get!(User, id) def get_user!(id), do: Repo.get!(User, id)
def get_user(id) do
User
|> where([u], is_nil(u.deleted_at))
|> Repo.get(id)
end
@doc """
Soft deletes a user.
## Examples
iex> delete_user(user)
{:ok, %User{}}
iex> delete_user(user)
{:error, %Ecto.Changeset{}}
"""
def delete_user(%User{} = user) do
user
|> User.delete_changeset()
|> Repo.update()
end
@doc """
Returns true if the user has been soft deleted.
"""
def deleted?(%User{} = user) do
not is_nil(user.deleted_at)
end
## User registration ## User registration
@doc """ @doc """
@@ -515,10 +548,6 @@ defmodule Claper.Accounts do
) )
end end
def delete(user) do
Repo.delete(user)
end
## OIDC ## OIDC
def create_oidc_user(attrs) do def create_oidc_user(attrs) do

View File

@@ -14,7 +14,8 @@ defmodule Claper.Accounts.User do
locale: String.t() | nil, locale: String.t() | nil,
events: [Claper.Events.Event.t()] | nil, events: [Claper.Events.Event.t()] | nil,
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t(),
deleted_at: NaiveDateTime.t() | nil
} }
schema "users" do schema "users" do
@@ -25,6 +26,7 @@ defmodule Claper.Accounts.User do
field :is_randomized_password, :boolean field :is_randomized_password, :boolean
field :confirmed_at, :naive_datetime field :confirmed_at, :naive_datetime
field :locale, :string field :locale, :string
field :deleted_at, :naive_datetime
has_many :events, Claper.Events.Event has_many :events, Claper.Events.Event
has_one :lti_user, Lti13.Users.User has_one :lti_user, Lti13.Users.User
@@ -44,6 +46,14 @@ defmodule Claper.Accounts.User do
|> cast(attrs, [:locale]) |> cast(attrs, [:locale])
end end
@doc """
A changeset for marking a user as deleted.
"""
def delete_changeset(user) do
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
change(user, deleted_at: now)
end
defp validate_email(changeset) do defp validate_email(changeset) do
changeset changeset
|> validate_required([:email]) |> validate_required([:email])

View File

@@ -75,7 +75,8 @@ defmodule Claper.Accounts.UserToken do
query = query =
from token in token_and_context_query(token, "session"), from token in token_and_context_query(token, "session"),
join: user in assoc(token, :user), join: user in assoc(token, :user),
where: token.inserted_at > ago(@session_validity_in_days, "day"), where:
token.inserted_at > ago(@session_validity_in_days, "day") and is_nil(user.deleted_at),
select: user select: user
{:ok, query} {:ok, query}

View File

@@ -43,6 +43,14 @@ defmodule ClaperWeb.UserRegistrationController do
end end
end end
def delete(conn, _params) do
Accounts.delete_user(conn.assigns.current_user)
conn
|> put_flash(:info, gettext("Your account has been deleted."))
|> UserAuth.log_out_user()
end
defp user_params(params) do defp user_params(params) do
if Application.get_env(:claper, :email_confirmation) do if Application.get_env(:claper, :email_confirmation) do
params params

View File

@@ -209,7 +209,7 @@
<% end %> <% end %>
</ul> </ul>
<%= if @page < @total_pages do %> <%= if @page < @total_pages do %>
<div class="flex justify-center mt-8"> <div class="flex justify-center my-4">
<button <button
phx-click="load-more" phx-click="load-more"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-primary-700 bg-primary-100 hover:bg-primary-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-primary-700 bg-primary-100 hover:bg-primary-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"

View File

@@ -210,16 +210,6 @@ defmodule ClaperWeb.UserSettingsLive.Show do
end end
end end
@impl true
def handle_event("delete_account", _params, %{assigns: %{current_user: user}} = socket) do
Accounts.delete(user)
{:noreply,
socket
|> put_flash(:info, gettext("Your account has been deleted."))
|> redirect(to: ~p"/users/log_in")}
end
@impl true @impl true
def handle_event("validate", _params, socket) do def handle_event("validate", _params, socket) do
{:noreply, socket} {:noreply, socket}

View File

@@ -280,16 +280,15 @@
</div> </div>
<div class="border-t border-gray-200 py-5 sm:p-0"> <div class="border-t border-gray-200 py-5 sm:p-0">
<dl class="sm:divide-y sm:divide-gray-200"> <dl class="sm:divide-y sm:divide-gray-200">
<div class="mt-5"> <div class="my-5">
<button <%= link(gettext("Delete account"),
data-confirm={ to: ~p"/users/register/delete",
gettext("All your events and files will be permanently deleted, are you sure?") method: :delete,
} "data-confirm":
phx-click="delete_account" gettext("All your events and files will be permanently deleted, are you sure?"),
class="w-full lg:w-auto px-6 text-center text-white py-2 rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline bg-gradient-to-tl from-supporting-red-600 to-supporting-red-400 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500" class:
> "w-full lg:w-auto px-6 text-center text-white py-2 rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline bg-gradient-to-tl from-supporting-red-600 to-supporting-red-400 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500"
<%= gettext("Delete account") %> ) %>
</button>
</div> </div>
</dl> </dl>
</div> </div>

View File

@@ -154,6 +154,7 @@ defmodule ClaperWeb.Router do
post("/events/:uuid/slide.jpg", EventController, :slide_generate) post("/events/:uuid/slide.jpg", EventController, :slide_generate)
get("/users/settings/confirm_email/:token", UserSettingsController, :confirm_email) get("/users/settings/confirm_email/:token", UserSettingsController, :confirm_email)
delete("/users/register/delete", UserRegistrationController, :delete)
end end
scope "/", ClaperWeb do scope "/", ClaperWeb do

View File

@@ -0,0 +1,11 @@
defmodule Claper.Repo.Migrations.AddDeletedAtToUsers do
use Ecto.Migration
def change do
alter table(:users) do
add :deleted_at, :naive_datetime, null: true
end
create index(:users, [:deleted_at])
end
end