feat(portal): Allow completely deleting accounts (#4557)

We need "ON DELETE CASCADE" everywhere to fully erase account-related
data. As a safeguard measure the account can only be deleted after its
subscription in Stripe is cancelled.
This commit is contained in:
Andrew Dryga
2024-04-09 14:43:22 -06:00
committed by GitHub
parent 0b1ffd1339
commit 439b9704b1
4 changed files with 168 additions and 0 deletions

View File

@@ -9,6 +9,10 @@ defmodule Domain.Accounts.Account.Query do
where(queryable, [accounts: accounts], is_nil(accounts.deleted_at))
end
def disabled(queryable \\ all()) do
where(queryable, [accounts: accounts], not is_nil(accounts.disabled_at))
end
def not_disabled(queryable \\ not_deleted()) do
where(queryable, [accounts: accounts], is_nil(accounts.disabled_at))
end

View File

@@ -83,4 +83,17 @@ defmodule Domain.Ops do
|> Domain.Billing.EventHandler.handle_event()
end)
end
@doc """
To delete an account you need to disable it first by cancelling its subscription in Stripe.
"""
def delete_disabled_account(id) do
Domain.Accounts.Account.Query.not_deleted()
|> Domain.Accounts.Account.Query.disabled()
|> Domain.Accounts.Account.Query.by_id(id)
|> Domain.Repo.one!()
|> Domain.Repo.delete()
:ok
end
end

View File

@@ -0,0 +1,117 @@
defmodule Domain.Repo.Migrations.AddAccountDeletionCascade do
use Ecto.Migration
def change do
execute("""
ALTER TABLE "actor_group_memberships"
DROP CONSTRAINT "actor_group_memberships_account_id_fkey",
ADD CONSTRAINT "actor_group_memberships_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "actors"
DROP CONSTRAINT "actors_account_id_fkey",
ADD CONSTRAINT "actors_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "auth_identities"
DROP CONSTRAINT "auth_identities_account_id_fkey",
ADD CONSTRAINT "auth_identities_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "auth_providers"
DROP CONSTRAINT "auth_providers_account_id_fkey",
ADD CONSTRAINT "auth_providers_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "clients"
DROP CONSTRAINT "devices_account_id_fkey",
ADD CONSTRAINT "devices_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "configurations"
DROP CONSTRAINT "configurations_account_id_fkey",
ADD CONSTRAINT "configurations_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "gateway_groups"
DROP CONSTRAINT "gateway_groups_account_id_fkey",
ADD CONSTRAINT "gateway_groups_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "gateways"
DROP CONSTRAINT "gateways_account_id_fkey",
ADD CONSTRAINT "gateways_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "network_addresses"
DROP CONSTRAINT "network_addresses_account_id_fkey",
ADD CONSTRAINT "network_addresses_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "policies"
DROP CONSTRAINT "policies_account_id_fkey",
ADD CONSTRAINT "policies_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "relay_groups"
DROP CONSTRAINT "relay_groups_account_id_fkey",
ADD CONSTRAINT "relay_groups_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "relays"
DROP CONSTRAINT "relays_account_id_fkey",
ADD CONSTRAINT "relays_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "resource_connections"
DROP CONSTRAINT "resource_connections_account_id_fkey",
ADD CONSTRAINT "resource_connections_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
execute("""
ALTER TABLE "resources"
DROP CONSTRAINT "resources_account_id_fkey",
ADD CONSTRAINT "resources_account_id_fkey"
FOREIGN KEY ("account_id")
REFERENCES "accounts" ("id") ON DELETE CASCADE;
""")
end
end

View File

@@ -80,4 +80,38 @@ defmodule Domain.OpsTest do
end
end
end
describe "delete_disabled_account/1" do
test "doesn't delete an account that is not disabled" do
account = Fixtures.Accounts.create_account()
assert_raise Ecto.NoResultsError, fn ->
delete_disabled_account(account.id)
end
end
test "deletes account along with all related entities" do
account = Fixtures.Accounts.create_account()
Fixtures.Actors.create_group(account: account)
Fixtures.Actors.create_actor(account: account)
Fixtures.Auth.create_identity(account: account)
Fixtures.Clients.create_client(account: account)
Fixtures.Flows.create_activity(account: account)
Fixtures.Gateways.create_gateway(account: account)
Fixtures.Policies.create_policy(account: account)
Fixtures.Relays.create_relay(account: account)
Fixtures.Resources.create_resource(account: account)
Fixtures.Tokens.create_token(account: account)
Fixtures.Accounts.disable_account(account)
assert delete_disabled_account(account.id) == :ok
assert_raise Ecto.NoResultsError, fn ->
assert delete_disabled_account(account.id) == :ok
end
refute Repo.one(Domain.Accounts.Account)
end
end
end