From f3a36163b65218996becaaa0afefc400b3c5c159 Mon Sep 17 00:00:00 2001 From: Alex Lion Date: Sat, 28 Dec 2024 13:13:55 -0500 Subject: [PATCH] Add soft delete user account --- CHANGELOG.md | 1 + lib/claper/accounts.ex | 43 ++++++++++++++++--- lib/claper/accounts/user.ex | 12 +++++- lib/claper/accounts/user_token.ex | 3 +- .../user_registration_controller.ex | 8 ++++ .../live/event_live/index.html.heex | 2 +- .../live/user_settings_live/show.ex | 10 ----- .../live/user_settings_live/show.html.heex | 19 ++++---- lib/claper_web/router.ex | 1 + ...20241228162732_add_deleted_at_to_users.exs | 11 +++++ 10 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 priv/repo/migrations/20241228162732_add_deleted_at_to_users.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index d666a983..653d0d9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Improve design and UX for interactions and presentation settings in the manager view - Add pagination for events on the dashboard - Fix STMP adapter to work with secure connection +- Add soft delete for user accounts ## v2.2.0 diff --git a/lib/claper/accounts.ex b/lib/claper/accounts.ex index e47ebf55..f93508c3 100644 --- a/lib/claper/accounts.ex +++ b/lib/claper/accounts.ex @@ -38,7 +38,9 @@ defmodule Claper.Accounts 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 @doc """ @@ -79,8 +81,8 @@ defmodule Claper.Accounts do """ def get_user_by_email_and_password(email, password) when is_binary(email) and is_binary(password) do - user = Repo.get_by(User, email: email) - if User.valid_password?(user, password), do: user + user = User |> where([u], u.email == ^email and is_nil(u.deleted_at)) |> Repo.one() + if user && User.valid_password?(user, password), do: user end @doc """ @@ -99,6 +101,37 @@ defmodule Claper.Accounts do """ 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 @doc """ @@ -515,10 +548,6 @@ defmodule Claper.Accounts do ) end - def delete(user) do - Repo.delete(user) - end - ## OIDC def create_oidc_user(attrs) do diff --git a/lib/claper/accounts/user.ex b/lib/claper/accounts/user.ex index 3ed30f8d..d3423078 100644 --- a/lib/claper/accounts/user.ex +++ b/lib/claper/accounts/user.ex @@ -14,7 +14,8 @@ defmodule Claper.Accounts.User do locale: String.t() | nil, events: [Claper.Events.Event.t()] | nil, inserted_at: NaiveDateTime.t(), - updated_at: NaiveDateTime.t() + updated_at: NaiveDateTime.t(), + deleted_at: NaiveDateTime.t() | nil } schema "users" do @@ -25,6 +26,7 @@ defmodule Claper.Accounts.User do field :is_randomized_password, :boolean field :confirmed_at, :naive_datetime field :locale, :string + field :deleted_at, :naive_datetime has_many :events, Claper.Events.Event has_one :lti_user, Lti13.Users.User @@ -44,6 +46,14 @@ defmodule Claper.Accounts.User do |> cast(attrs, [:locale]) 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 changeset |> validate_required([:email]) diff --git a/lib/claper/accounts/user_token.ex b/lib/claper/accounts/user_token.ex index 4c2fed24..082aab1e 100644 --- a/lib/claper/accounts/user_token.ex +++ b/lib/claper/accounts/user_token.ex @@ -75,7 +75,8 @@ defmodule Claper.Accounts.UserToken do query = from token in token_and_context_query(token, "session"), 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 {:ok, query} diff --git a/lib/claper_web/controllers/user_registration_controller.ex b/lib/claper_web/controllers/user_registration_controller.ex index d830595b..019fa71b 100644 --- a/lib/claper_web/controllers/user_registration_controller.ex +++ b/lib/claper_web/controllers/user_registration_controller.ex @@ -43,6 +43,14 @@ defmodule ClaperWeb.UserRegistrationController do 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 if Application.get_env(:claper, :email_confirmation) do params diff --git a/lib/claper_web/live/event_live/index.html.heex b/lib/claper_web/live/event_live/index.html.heex index 3458ab0e..b54c689e 100644 --- a/lib/claper_web/live/event_live/index.html.heex +++ b/lib/claper_web/live/event_live/index.html.heex @@ -209,7 +209,7 @@ <% end %> <%= if @page < @total_pages do %> -
+
-
- +
+ <%= link(gettext("Delete account"), + to: ~p"/users/register/delete", + method: :delete, + "data-confirm": + 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" + ) %>
diff --git a/lib/claper_web/router.ex b/lib/claper_web/router.ex index 03af1b20..7d06ba06 100644 --- a/lib/claper_web/router.ex +++ b/lib/claper_web/router.ex @@ -154,6 +154,7 @@ defmodule ClaperWeb.Router do post("/events/:uuid/slide.jpg", EventController, :slide_generate) get("/users/settings/confirm_email/:token", UserSettingsController, :confirm_email) + delete("/users/register/delete", UserRegistrationController, :delete) end scope "/", ClaperWeb do diff --git a/priv/repo/migrations/20241228162732_add_deleted_at_to_users.exs b/priv/repo/migrations/20241228162732_add_deleted_at_to_users.exs new file mode 100644 index 00000000..73cbb4d2 --- /dev/null +++ b/priv/repo/migrations/20241228162732_add_deleted_at_to_users.exs @@ -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