M assets/css/app.scss => assets/css/app.scss +2 -1
@@ 197,7 197,7 @@ fieldset {
border-color: transparent;
border-width: 0;
border-style: solid;
- margin: 0;
+ margin: 0 0 $spacing-small;
padding: 0;
& input, & textarea {
@@ 228,6 228,7 @@ fieldset {
& input[type=checkbox] {
margin-bottom: 0;
margin-right: $spacing-small;
+ margin-left: 0;
vertical-align: middle;
}
M lib/linkhut/accounts.ex => lib/linkhut/accounts.ex +16 -5
@@ 122,16 122,27 @@ defmodule Linkhut.Accounts do
## Examples
- iex> delete_user(user)
+ iex> delete_user(user, %{"confirmed" => "true"})
{:ok, %User{}}
- iex> delete_user(user)
+ iex> delete_user(user, %{})
{:error, %Ecto.Changeset{}}
"""
- @spec delete_user(User.t()) :: {:ok, User.t()} | {:error, changeset(User.t())}
- def delete_user(%User{} = user) do
- Repo.delete(user)
+ @spec delete_user(changeset(User.t()), %{optional(any) => any}) ::
+ {:ok, User.t()} | {:error, changeset(User.t())}
+ def delete_user(%User{} = user, attrs) do
+ user
+ |> Repo.preload(:credential)
+ |> User.changeset(attrs)
+ |> Ecto.Changeset.validate_acceptance(:confirmed,
+ message: "Please confirm you want to delete your account"
+ )
+ |> Ecto.Changeset.no_assoc_constraint(:applications,
+ message:
+ "You still own OAuth applications, you must delete those before deleting your account"
+ )
+ |> Repo.delete()
end
@doc """
M lib/linkhut/accounts/user.ex => lib/linkhut/accounts/user.ex +13 -1
@@ 22,7 22,19 @@ defmodule Linkhut.Accounts.User do
field :roles, {:array, Ecto.Enum}, values: [:admin], default: []
has_one :credential, Credential
- has_many :links, Link, references: :id
+ has_many :links, Link, references: :id, on_delete: :delete_all
+
+ has_many :applications, Linkhut.Oauth.Application, foreign_key: :owner_id, references: :id
+
+ has_many :access_grants, Linkhut.Oauth.AccessGrant,
+ foreign_key: :resource_owner_id,
+ references: :id,
+ on_delete: :delete_all
+
+ has_many :access_tokens, Linkhut.Oauth.AccessToken,
+ foreign_key: :resource_owner_id,
+ references: :id,
+ on_delete: :delete_all
timestamps(type: :utc_datetime)
end
M lib/linkhut_web/controllers/settings/profile_controller.ex => lib/linkhut_web/controllers/settings/profile_controller.ex +22 -0
@@ 21,6 21,28 @@ defmodule LinkhutWeb.Settings.ProfileController do
|> update(conn.assigns[:current_user], user_params)
end
+ require Logger
+
+ def delete(conn, %{"user" => user_params}) do
+ user = conn.assigns[:current_user]
+
+ case Accounts.delete_user(user, user_params) do
+ {:ok, user} ->
+ conn
+ |> put_flash(:info, "Deleted account for #{user.username}")
+ |> redirect(to: "/")
+ |> configure_session(drop: true)
+
+ {:error, changeset} ->
+ conn
+ |> render("profile.html",
+ user: user,
+ changeset: changeset,
+ current_email_unconfirmed?: Accounts.current_email_unconfirmed?(user)
+ )
+ end
+ end
+
defp update(conn, user, params) when not is_nil(user) do
case Accounts.update_user(user, params) do
{:ok, user} ->
M lib/linkhut_web/router.ex => lib/linkhut_web/router.ex +1 -0
@@ 116,6 116,7 @@ defmodule LinkhutWeb.Router do
get "/misc", MiscController, :show
get "/profile", ProfileController, :show
put "/profile", ProfileController, :update
+ put "/profile/delete", ProfileController, :delete
post "/confirm", EmailConfirmationController, :create
end
M lib/linkhut_web/templates/settings/oauth/authorized_applications/_index.html.heex => lib/linkhut_web/templates/settings/oauth/authorized_applications/_index.html.heex +1 -1
@@ 17,7 17,7 @@
<td><%= prettify(List.first(application.access_tokens).inserted_at) %></td>
<td><%= prettify(NaiveDateTime.add(List.last(application.access_tokens).inserted_at, List.last(application.access_tokens).expires_in, :second)) %></td>
<td>
- <%= form_for %{uid: application.uid}, Routes.oauth_path(@conn, :revoke_access, application.uid), [class: "inline"], fn f -> %>
+ <%= form_for %{"uid" => application.uid}, Routes.oauth_path(@conn, :revoke_access, application.uid), [class: "inline"], fn f -> %>
<%= hidden_input(f, :uid) %>
<%= submit("Revoke") %>
<% end %>
M lib/linkhut_web/templates/settings/profile.html.heex => lib/linkhut_web/templates/settings/profile.html.heex +10 -0
@@ 30,4 30,14 @@
<%= submit("Save") %>
<% end %>
</section>
+ <section class="settings">
+ <h4>Delete Account</h4>
+ <%= form_for @changeset, Routes.profile_path(@conn, :delete), fn f -> %>
+ <fieldset>
+ <%= input(f, :confirmed, type: :checkbox, label: "I acknowledge that I want to permanently delete my account and all data associated with it") %>
+ <%= error_tag(f, :applications) %>
+ </fieldset>
+ <%= submit("Delete") %>
+ <% end %>
+ </section>
</div>
M lib/linkhut_web/views/error_helpers.ex => lib/linkhut_web/views/error_helpers.ex +13 -0
@@ 5,6 5,19 @@ defmodule LinkhutWeb.ErrorHelpers do
use Phoenix.HTML
@doc """
+ Generates tag for inlined form input errors.
+ """
+ def error_tag(form, field) do
+ if error = form.errors[field] do
+ content_tag(
+ :div,
+ content_tag(:ul, content_tag(:li, translate_error(error), class: "invalid")),
+ class: "invalid"
+ )
+ end
+ end
+
+ @doc """
Translates an error message using gettext.
"""
def translate_error({msg, opts}) do
M lib/linkhut_web/views/settings_view.ex => lib/linkhut_web/views/settings_view.ex +2 -0
@@ 2,6 2,8 @@ defmodule LinkhutWeb.SettingsView do
use LinkhutWeb, :view
use Phoenix.HTML
+ import LinkhutWeb.FormHelpers
+ import LinkhutWeb.ErrorHelpers
@doc """
Generates a bookmarklet link to add the current page to this linkhut instance