-
<%= t(".delete_account") %>
-
<%= t(".delete_account_warning") %>
+
+ <% if Current.user.admin? %>
+
+
+
<%= t(".reset_account") %>
+
<%= t(".reset_account_warning") %>
+
+ <%=
+ button_to t(".reset_account"), reset_user_path(@user), method: :delete,
+ class: "bg-orange-500 text-white text-sm font-medium rounded-lg px-4 py-2",
+ data: { turbo_confirm: {
+ title: t(".confirm_reset.title"),
+ body: t(".confirm_reset.body"),
+ accept: t(".reset_account"),
+ acceptClass: "w-full bg-orange-500 text-white rounded-xl text-center p-[10px] border mb-2"
+ }}
+ %>
+
+ <% end %>
+
+
+
<%= t(".delete_account") %>
+
<%= t(".delete_account_warning") %>
+
+ <%=
+ button_to t(".delete_account"), user_path(@user), method: :delete,
+ class: "bg-red-500 text-white text-sm font-medium rounded-lg px-3 py-2",
+ data: { turbo_confirm: {
+ title: t(".confirm_delete.title"),
+ body: t(".confirm_delete.body"),
+ accept: t(".delete_account"),
+ acceptClass: "w-full bg-red-500 text-white rounded-xl text-center p-[10px] border mb-2"
+ }}
+ %>
- <%=
- button_to t(".delete_account"), user_path(@user), method: :delete,
- class: "bg-red-500 text-white text-sm font-medium rounded-lg px-3 py-2",
- data: { turbo_confirm: {
- title: t(".confirm_delete.title"),
- body: t(".confirm_delete.body"),
- accept: t(".delete_account"),
- acceptClass: "w-full bg-red-500 text-white rounded-xl text-center p-[10px] border mb-2"
- }}
- %>
<% end %>
diff --git a/config/locales/views/settings/en.yml b/config/locales/views/settings/en.yml
index e7e7d627..8596904e 100644
--- a/config/locales/views/settings/en.yml
+++ b/config/locales/views/settings/en.yml
@@ -39,6 +39,9 @@ en:
body: Are you sure you want to permanently delete your account? This action
is irreversible.
title: Delete account?
+ confirm_reset:
+ body: Are you sure you want to reset your account? This will delete all your accounts, categories, merchants, tags, and other data. This action cannot be undone.
+ title: Reset account?
confirm_remove_invitation:
body: Are you sure you want to remove the invitation for %{email}?
title: Remove Invitation
@@ -49,6 +52,8 @@ en:
delete_account: Delete account
delete_account_warning: Deleting your account will permanently remove all
your data and cannot be undone.
+ reset_account: Reset account
+ reset_account_warning: Resetting your account will delete all your accounts, categories, merchants, tags, and other data, but keep your user account intact.
email: Email
first_name: First Name
household_form_input_placeholder: Enter household name
diff --git a/config/locales/views/settings/hostings/en.yml b/config/locales/views/settings/hostings/en.yml
index 8cb7f6c4..bbcb8ee9 100644
--- a/config/locales/views/settings/hostings/en.yml
+++ b/config/locales/views/settings/hostings/en.yml
@@ -20,6 +20,12 @@ en:
general: General Settings
invites: Invite Codes
title: Self-Hosting
+ danger_zone: Danger Zone
+ clear_cache: Clear data cache
+ clear_cache_warning: Clearing the data cache will remove all exchange rates, security prices, account balances, and other data. This will not delete accounts, transactions, categories, or other user-owned data.
+ confirm_clear_cache:
+ title: Clear data cache?
+ body: Are you sure you want to clear the data cache? This will remove all exchange rates, security prices, account balances, and other data. This action cannot be undone.
synth_settings:
api_calls_used: "%{used} / %{limit} API calls used (%{percentage})"
description: Input the API key provided by Synth
@@ -30,6 +36,8 @@ en:
update:
failure: Invalid setting value
success: Settings updated
+ clear_cache:
+ cache_cleared: Data cache has been cleared. This may take a few moments to complete.
upgrade_settings:
description: Configure how your application receives updates
latest_commit_description: Automatically update to the latest commit (unstable)
@@ -40,3 +48,4 @@ en:
manual_description: You control when to download and install updates
manual_title: Manual
title: Auto Upgrade
+ not_authorized: You are not authorized to perform this action
diff --git a/config/locales/views/users/en.yml b/config/locales/views/users/en.yml
index e9aae045..e04ebc8c 100644
--- a/config/locales/views/users/en.yml
+++ b/config/locales/views/users/en.yml
@@ -8,3 +8,6 @@ en:
email_change_initiated: Please check your new email address for confirmation
instructions.
success: Your profile has been updated.
+ reset:
+ success: Your account has been reset. Data will be deleted in the background in some time.
+ unauthorized: You are not authorized to perform this action
diff --git a/config/routes.rb b/config/routes.rb
index 13f9f61f..30d3303e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -18,7 +18,9 @@ Rails.application.routes.draw do
resource :password, only: %i[edit update]
resource :email_confirmation, only: :new
- resources :users, only: %i[update destroy]
+ resources :users, only: %i[update destroy] do
+ delete :reset, on: :member
+ end
resource :onboarding, only: :show do
collection do
@@ -30,7 +32,9 @@ Rails.application.routes.draw do
namespace :settings do
resource :profile, only: [ :show, :destroy ]
resource :preferences, only: :show
- resource :hosting, only: %i[show update]
+ resource :hosting, only: %i[show update] do
+ delete :clear_cache, on: :collection
+ end
resource :billing, only: :show
resource :security, only: :show
end
diff --git a/test/controllers/settings/hostings_controller_test.rb b/test/controllers/settings/hostings_controller_test.rb
index 283b70bc..3ee8a226 100644
--- a/test/controllers/settings/hostings_controller_test.rb
+++ b/test/controllers/settings/hostings_controller_test.rb
@@ -45,4 +45,39 @@ class Settings::HostingsControllerTest < ActionDispatch::IntegrationTest
assert_equal NEW_RENDER_DEPLOY_HOOK, Setting.render_deploy_hook
end
end
+
+ test "can clear data cache when self hosting is enabled" do
+ account = accounts(:investment)
+ holding = account.holdings.first
+ exchange_rate = exchange_rates(:one)
+ security_price = holding.security.prices.first
+ account_balance = account.balances.create!(date: Date.current, balance: 1000, currency: "USD")
+
+ with_self_hosting do
+ perform_enqueued_jobs(only: DataCacheClearJob) do
+ delete clear_cache_settings_hosting_url
+ end
+ end
+
+ assert_redirected_to settings_hosting_url
+ assert_equal I18n.t("settings.hostings.clear_cache.cache_cleared"), flash[:notice]
+
+ assert_not ExchangeRate.exists?(exchange_rate.id)
+ assert_not Security::Price.exists?(security_price.id)
+ assert_not Account::Holding.exists?(holding.id)
+ assert_not Account::Balance.exists?(account_balance.id)
+ end
+
+ test "can clear data only when admin" do
+ with_self_hosting do
+ sign_in users(:family_member)
+
+ assert_no_enqueued_jobs do
+ delete clear_cache_settings_hosting_url
+ end
+
+ assert_redirected_to settings_hosting_url
+ assert_equal I18n.t("settings.hostings.not_authorized"), flash[:alert]
+ end
+ end
end
diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb
index 74e217f2..bd68fe77 100644
--- a/test/controllers/users_controller_test.rb
+++ b/test/controllers/users_controller_test.rb
@@ -31,6 +31,41 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
assert_equal "Your profile has been updated.", flash[:notice]
end
+ test "admin can reset family data" do
+ account = accounts(:investment)
+ category = categories(:income)
+ tag = tags(:one)
+ merchant = merchants(:netflix)
+ import = imports(:transaction)
+ budget = budgets(:one)
+ plaid_item = plaid_items(:one)
+
+ perform_enqueued_jobs(only: FamilyResetJob) do
+ delete reset_user_url(@user)
+ end
+
+ assert_redirected_to settings_profile_url
+ assert_equal I18n.t("users.reset.success"), flash[:notice]
+
+ assert_not Account.exists?(account.id)
+ assert_not Category.exists?(category.id)
+ assert_not Tag.exists?(tag.id)
+ assert_not Merchant.exists?(merchant.id)
+ assert_not Import.exists?(import.id)
+ assert_not Budget.exists?(budget.id)
+ assert_not PlaidItem.exists?(plaid_item.id)
+ end
+
+ test "non-admin cannot reset family data" do
+ sign_in @member = users(:family_member)
+
+ delete reset_user_url(@member)
+
+ assert_redirected_to settings_profile_url
+ assert_equal I18n.t("users.reset.unauthorized"), flash[:alert]
+ assert_no_enqueued_jobs only: FamilyResetJob
+ end
+
test "member can deactivate their account" do
sign_in @member = users(:family_member)
delete user_url(@member)