feat(portal): enable outdated gateway email (#10281)

Enables 'outdated gateway' notifications for all accounts.

Closes #8361
This commit is contained in:
Brian Manifold
2025-09-03 20:56:01 -07:00
committed by GitHub
parent eeadde0c86
commit 826a304071
8 changed files with 112 additions and 18 deletions

View File

@@ -32,6 +32,14 @@ defmodule Domain.Accounts do
|> Repo.all()
end
# TODO: This will need to be updated once more notifications are available
def all_accounts_pending_notification! do
Account.Query.not_disabled()
|> Account.Query.by_notification_enabled("outdated_gateway")
|> Account.Query.by_notification_last_notified("outdated_gateway", 24)
|> Repo.all()
end
def fetch_account_by_id(id, %Auth.Subject{} = subject, opts \\ []) do
with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_own_account_permission()),
true <- Repo.valid_uuid?(id) do

View File

@@ -18,6 +18,7 @@ defmodule Domain.Accounts.Account.Changeset do
%Account{}
|> cast(attrs, [:name, :legal_name, :slug])
|> changeset()
|> put_default_config()
end
def update_profile_and_config(%Account{} = account, attrs) do
@@ -121,4 +122,23 @@ defmodule Domain.Accounts.Account.Changeset do
{:error, "Account ID or Slug contains invalid characters"}
end
end
defp put_default_config(changeset) do
case get_change(changeset, :config) do
nil ->
put_change(changeset, :config, Config.default_config())
%Ecto.Changeset{} = config_changeset ->
config = Ecto.Changeset.apply_changes(config_changeset)
updated_config = Config.ensure_defaults(config)
put_change(changeset, :config, updated_config)
%Config{} = config ->
updated_config = Config.ensure_defaults(config)
put_change(changeset, :config, updated_config)
_ ->
changeset
end
end
end

View File

@@ -23,4 +23,37 @@ defmodule Domain.Accounts.Config do
end
def supported_dns_protocols, do: ~w[ip_port]a
@doc """
Returns a default config with defaults set
"""
def default_config do
%__MODULE__{
notifications: %__MODULE__.Notifications{
outdated_gateway: %Domain.Accounts.Config.Notifications.Email{enabled: true}
}
}
end
@doc """
Ensures a config has proper defaults
"""
def ensure_defaults(%__MODULE__{} = config) do
notifications = config.notifications || %__MODULE__.Notifications{}
outdated_gateway =
notifications.outdated_gateway || %Domain.Accounts.Config.Notifications.Email{enabled: true}
outdated_gateway =
case outdated_gateway.enabled do
nil -> %{outdated_gateway | enabled: true}
_ -> outdated_gateway
end
notifications = %{notifications | outdated_gateway: outdated_gateway}
%{config | notifications: notifications}
end
def ensure_defaults(nil), do: default_config()
end

View File

@@ -4,7 +4,7 @@ defmodule Domain.Accounts.Config.Notifications.Email do
@primary_key false
embedded_schema do
field :enabled, :boolean
field :enabled, :boolean, default: true
field :last_notified, :utc_datetime
end
end

View File

@@ -91,6 +91,6 @@ defmodule Domain.Mailer do
|> Keyword.fetch!(:from_email)
Email.new()
|> Email.from(from_email)
|> Email.from({"Firezone Notifications", from_email})
end
end

View File

@@ -24,7 +24,7 @@ defmodule Domain.Notifications.Jobs.OutdatedGateways do
end
defp run_check do
Accounts.all_active_paid_accounts_pending_notification!()
Accounts.all_accounts_pending_notification!()
|> Enum.each(fn account ->
all_online_gateways_for_account(account)
|> Enum.filter(&Gateways.gateway_outdated?/1)

View File

@@ -0,0 +1,47 @@
defmodule Domain.Repo.Migrations.EnableOutdatedGatewayEmailByDefault do
use Ecto.Migration
def up do
execute("""
UPDATE accounts
SET config = jsonb_set(
jsonb_set(
COALESCE(config, '{}'::jsonb),
'{notifications}',
CASE
WHEN config->'notifications' IS NULL OR config->'notifications' = 'null'::jsonb THEN '{}'::jsonb
ELSE config->'notifications'
END,
true
),
'{notifications,outdated_gateway}',
jsonb_build_object('enabled', true),
true
)
WHERE deleted_at IS NULL
AND disabled_at IS NULL
""")
end
def down do
execute("""
UPDATE accounts
SET config = jsonb_set(
jsonb_set(
COALESCE(config, '{}'::jsonb),
'{notifications}',
CASE
WHEN config->'notifications' IS NULL OR config->'notifications' = 'null'::jsonb THEN '{}'::jsonb
ELSE config->'notifications'
END,
true
),
'{notifications,outdated_gateway}',
jsonb_build_object('enabled', false),
true
)
WHERE deleted_at IS NULL
AND disabled_at IS NULL
""")
end
end

View File

@@ -53,13 +53,9 @@ defmodule Web.Settings.Account do
Notifications
</:title>
<:action>
<.edit_button
:if={@account_type != "Starter"}
navigate={~p"/#{@account}/settings/account/notifications/edit"}
>
<.edit_button navigate={~p"/#{@account}/settings/account/notifications/edit"}>
Edit Notifications
</.edit_button>
<.upgrade_badge :if={@account_type == "Starter"} account={@account} />
</:action>
<:content>
<div class="relative overflow-x-auto">
@@ -155,16 +151,6 @@ defmodule Web.Settings.Account do
"""
end
defp upgrade_badge(assigns) do
~H"""
<.link navigate={~p"/#{@account}/settings/billing"} class="text-sm text-primary-500">
<.badge type="primary" title="Feature available on a higher pricing plan">
<.icon name="hero-lock-closed" class="w-3.5 h-3.5 mr-1" /> UPGRADE TO UNLOCK
</.badge>
</.link>
"""
end
defp notification_badge(assigns) do
~H"""
<.badge type={if @notification.enabled, do: "success", else: "neutral"}>