Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Teams writes switch #4883

Merged
merged 51 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
6cf6314
Comment out legacy fields and relationships
zoldar Dec 5, 2024
f11d709
WIP
zoldar Dec 5, 2024
cb1ae4d
WIP 2
zoldar Dec 9, 2024
7d48e0a
WIP 3
zoldar Dec 9, 2024
f9116b8
wip
aerosol Dec 9, 2024
f8fae57
Remove teams backfill and consistency check scripts
zoldar Dec 9, 2024
9510b4c
WIP 3
zoldar Dec 9, 2024
9e53b9c
Fix CheckUsage tests
aerosol Dec 9, 2024
a57d0ee
Update billing/subscription tests
aerosol Dec 9, 2024
feb0e0e
WIP 4
zoldar Dec 9, 2024
d0ef203
Make site transfer fail if some invitation already exists
aerosol Dec 10, 2024
4573939
Fixup: do symmetric invitation/site transfer check
aerosol Dec 10, 2024
faa546c
Update UI bugs: make listing sites/inviting admins work like before
aerosol Dec 10, 2024
45711ac
Fix Sites test
aerosol Dec 10, 2024
90fb543
Fix external sites controller test
aerosol Dec 10, 2024
965395e
Fix live sites tests
aerosol Dec 10, 2024
317e576
Fix props availability lookup
aerosol Dec 10, 2024
f3f0874
Fix site controller tests
aerosol Dec 10, 2024
e687623
Fix billing controller tests
aerosol Dec 10, 2024
491dfa1
WIP - accept invitation tests
aerosol Dec 10, 2024
8e35f8f
Another round of test fixes + invitations logic bugs
zoldar Dec 10, 2024
a2fb761
users_test -> teams_test
aerosol Dec 10, 2024
148ae9c
Update registration via invitation
aerosol Dec 10, 2024
3ae5c40
Yet another round of test and bugfixes along the way
zoldar Dec 10, 2024
6c7a825
Include team in site setup success e-mail
aerosol Dec 10, 2024
5ce50ec
Fix send_site_setup_emails worker
aerosol Dec 10, 2024
1267eb2
Fixed almost all tests except CRM ones
zoldar Dec 10, 2024
2837648
Update enterprise plan admin test
aerosol Dec 11, 2024
b2ee4ca
Fix CRM + remaining tests
aerosol Dec 11, 2024
1ed0633
Address credo warnings (modulo one FIXME)
aerosol Dec 11, 2024
dad5f94
Remove last FIXME and rephrase the invitation test case description
zoldar Dec 11, 2024
dd22655
Set Team fields via User CRM transparently
zoldar Dec 11, 2024
52e874d
Map user reference in Enterprise Plan CRM via team owner
zoldar Dec 11, 2024
27e7751
Fix resource actions in user CRM
zoldar Dec 11, 2024
399b267
Get rid of warning when opening create form in API keys CRM
zoldar Dec 11, 2024
3aacee9
Stop emitting warnings when editing Enterprise Plans via CRM
aerosol Dec 11, 2024
162d892
Tests: Bump await_clickhouse_count interval
aerosol Dec 11, 2024
6f1ec3b
Remove XXX marker
aerosol Dec 11, 2024
6b4cd78
Fix register from invitation link in email sent for ownership transfer
zoldar Dec 11, 2024
7b60bfd
Simplify fetching all pending site ownership site IDs
zoldar Dec 11, 2024
84ae6ff
Remove commented out schema fields
zoldar Dec 11, 2024
6ea8758
Remove unused functions
aerosol Dec 11, 2024
60dc1e4
Address flakiness in ingest counter tests
zoldar Dec 11, 2024
c8446a1
Remove unused `Teams.Sites.create`
zoldar Dec 12, 2024
8c1278d
Don't restart trial on team with subscription when creating site
zoldar Dec 12, 2024
2c0b328
Account for cases of legacy teams with empty trial expiry date
zoldar Dec 12, 2024
58bc967
Revert "Address flakiness in ingest counter tests"
aerosol Dec 16, 2024
f7c8d60
Fix flaky ingest counters tests under load
aerosol Dec 16, 2024
bab2ddc
Attempt 2
aerosol Dec 16, 2024
712c66a
Pre-emptively hardcode site ids in sampling cache test
aerosol Dec 16, 2024
848d1f4
Fix ingest counter tests by accounting for delayed summation
zoldar Dec 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions extra/lib/plausible/funnels.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ defmodule Plausible.Funnels do
| {:error, Ecto.Changeset.t() | :invalid_funnel_size | :upgrade_required}
def create(site, name, steps)
when is_list(steps) and length(steps) in Funnel.min_steps()..Funnel.max_steps() do
site = Plausible.Repo.preload(site, :owner)
site = Plausible.Repo.preload(site, :team)

case Plausible.Billing.Feature.Funnels.check_availability(site.owner) do
case Plausible.Billing.Feature.Funnels.check_availability(site.team) do
{:error, _} = error ->
error

Expand All @@ -39,9 +39,9 @@ defmodule Plausible.Funnels do
{:ok, Funnel.t()}
| {:error, Ecto.Changeset.t() | :invalid_funnel_size | :upgrade_required}
def update(funnel, name, steps) do
site = Plausible.Repo.preload(funnel, site: :owner).site
site = Plausible.Repo.preload(funnel, site: :team).site

case Plausible.Billing.Feature.Funnels.check_availability(site.owner) do
case Plausible.Billing.Feature.Funnels.check_availability(site.team) do
{:error, _} = error ->
error

Expand Down
4 changes: 2 additions & 2 deletions extra/lib/plausible/stats/goal/revenue.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ defmodule Plausible.Stats.Goal.Revenue do
end

def available?(site) do
site = Plausible.Repo.preload(site, :owner)
Plausible.Billing.Feature.RevenueGoals.check_availability(site.owner) == :ok
site = Plausible.Repo.preload(site, :team)
Plausible.Billing.Feature.RevenueGoals.check_availability(site.team) == :ok
end

# :NOTE: Legacy queries don't have metrics associated with them so work around the issue by assuming
Expand Down
4 changes: 1 addition & 3 deletions lib/mix/tasks/create_free_subscription.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ defmodule Mix.Tasks.CreateFreeSubscription do
user = Repo.get(Plausible.Auth.User, user_id)
{:ok, team} = Plausible.Teams.get_or_create(user)

Subscription.free(%{user_id: user_id, team_id: team.id})
Subscription.free(%{team_id: team.id})
|> Repo.insert!()

Plausible.Teams.sync_team(user)

IO.puts("Created a free subscription for user: #{user.name}")
end
end
2 changes: 0 additions & 2 deletions lib/mix/tasks/pull_sandbox_subscription.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,12 @@ defmodule Mix.Tasks.PullSandboxSubscription do
res = body["response"] |> List.first()
user = Repo.get_by!(User, email: res["user_email"])
{:ok, team} = Plausible.Teams.get_or_create(user)
Plausible.Teams.sync_team(user)

subscription = %{
paddle_subscription_id: res["subscription_id"] |> to_string(),
paddle_plan_id: res["plan_id"] |> to_string(),
cancel_url: res["cancel_url"],
update_url: res["update_url"],
user_id: user.id,
team_id: team.id,
status: res["state"],
last_bill_date: res["last_payment"]["date"],
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible/auth/api_key_admin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Plausible.Auth.ApiKeyAdmin do

def create_changeset(schema, attrs) do
scopes = [attrs["scope"]]
Plausible.Auth.ApiKey.changeset(schema, Map.merge(%{"scopes" => scopes}, attrs))
Plausible.Auth.ApiKey.changeset(struct(schema, %{}), Map.merge(%{"scopes" => scopes}, attrs))
end

def update_changeset(schema, attrs) do
Expand Down
24 changes: 0 additions & 24 deletions lib/plausible/auth/auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ defmodule Plausible.Auth do

@type rate_limit_type() :: unquote(Enum.reduce(@rate_limit_types, &{:|, [], [&1, &2]}))

@spec rate_limits() :: map()
def rate_limits(), do: @rate_limits

@spec rate_limit(rate_limit_type(), Auth.User.t() | Plug.Conn.t()) ::
:ok | {:error, {:rate_limit, rate_limit_type()}}
def rate_limit(limit_type, key) when limit_type in @rate_limit_types do
Expand All @@ -50,11 +47,6 @@ defmodule Plausible.Auth do
end
end

def create_user(name, email, pwd) do
Auth.User.new(%{name: name, email: email, password: pwd, password_confirmation: pwd})
|> Repo.insert()
end

@spec find_user_by(Keyword.t()) :: Auth.User.t() | nil
def find_user_by(opts) do
Repo.get_by(Auth.User, opts)
Expand All @@ -77,22 +69,6 @@ defmodule Plausible.Auth do
end
end

def has_active_sites?(user, roles \\ [:owner, :admin, :viewer]) do
sites =
Repo.all(
from u in Plausible.Auth.User,
where: u.id == ^user.id,
join: sm in Plausible.Site.Membership,
on: sm.user_id == u.id,
where: sm.role in ^roles,
join: s in Plausible.Site,
on: s.id == sm.site_id,
select: s
)

Enum.any?(sites, &Plausible.Sites.has_stats?/1)
end

def delete_user(user) do
Repo.transaction(fn ->
case Plausible.Teams.get_by_owner(user) do
Expand Down
38 changes: 19 additions & 19 deletions lib/plausible/auth/grace_period.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule Plausible.Auth.GracePeriod do
"""

use Ecto.Schema
alias Plausible.Auth.User
alias Plausible.Teams

@type t() :: %__MODULE__{
end_date: Date.t() | nil,
Expand All @@ -27,59 +27,59 @@ defmodule Plausible.Auth.GracePeriod do
field :manual_lock, :boolean
end

@spec start_changeset(User.t()) :: Ecto.Changeset.t()
@spec start_changeset(Teams.Team.t()) :: Ecto.Changeset.t()
@doc """
Starts a account locking grace period of 7 days by changing the User struct.
"""
def start_changeset(%User{} = user) do
def start_changeset(%Teams.Team{} = team) do
grace_period = %__MODULE__{
end_date: Date.shift(Date.utc_today(), day: 7),
is_over: false,
manual_lock: false
}

Ecto.Changeset.change(user, grace_period: grace_period)
Ecto.Changeset.change(team, grace_period: grace_period)
end

@spec start_manual_lock_changeset(User.t()) :: Ecto.Changeset.t()
@spec start_manual_lock_changeset(Teams.Team.t()) :: Ecto.Changeset.t()
@doc """
Starts a manual account locking grace period by changing the User struct.
Manual locking means the grace period can only be removed manually from the
CRM.
"""
def start_manual_lock_changeset(%User{} = user) do
def start_manual_lock_changeset(%Teams.Team{} = team) do
grace_period = %__MODULE__{
end_date: nil,
is_over: false,
manual_lock: true
}

Ecto.Changeset.change(user, grace_period: grace_period)
Ecto.Changeset.change(team, grace_period: grace_period)
end

@spec end_changeset(User.t()) :: Ecto.Changeset.t()
@spec end_changeset(Teams.Team.t()) :: Ecto.Changeset.t()
@doc """
Ends an existing grace period by `setting users.grace_period.is_over` to true.
This means the grace period has expired.
"""
def end_changeset(%User{} = user) do
Ecto.Changeset.change(user, grace_period: %{is_over: true})
def end_changeset(%Teams.Team{} = team) do
Ecto.Changeset.change(team, grace_period: %{is_over: true})
end

@spec remove_changeset(User.t()) :: Ecto.Changeset.t()
@spec remove_changeset(Teams.Team.t()) :: Ecto.Changeset.t()
@doc """
Removes the grace period from the User completely.
"""
def remove_changeset(%User{} = user) do
Ecto.Changeset.change(user, grace_period: nil)
def remove_changeset(%Teams.Team{} = team) do
Ecto.Changeset.change(team, grace_period: nil)
end

@spec active?(User.t() | Plausible.Teams.Team.t()) :: boolean()
@spec active?(Teams.Team.t() | nil) :: boolean()
@doc """
Returns whether the grace period is still active for a User. Defaults to
false if the user is nil or there is no grace period.
"""
def active?(user_or_team)
def active?(team)

def active?(%{grace_period: %__MODULE__{end_date: %Date{} = end_date}}) do
Date.diff(end_date, Date.utc_today()) >= 0
Expand All @@ -89,14 +89,14 @@ defmodule Plausible.Auth.GracePeriod do
true
end

def active?(_user), do: false
def active?(_team), do: false

@spec expired?(User.t() | Plausible.Teams.Team.t() | nil) :: boolean()
@spec expired?(Teams.Team.t() | nil) :: boolean()
@doc """
Returns whether the grace period has already expired for a User. Defaults to
false if the user is nil or there is no grace period.
"""
def expired?(user_or_team) do
if user_or_team && user_or_team.grace_period, do: !active?(user_or_team), else: false
def expired?(team) do
if team && team.grace_period, do: !active?(team), else: false
end
end
30 changes: 0 additions & 30 deletions lib/plausible/auth/invitation.ex

This file was deleted.

70 changes: 8 additions & 62 deletions lib/plausible/auth/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ defmodule Plausible.Auth.User do

@required [:email, :name, :password]

@trial_accept_traffic_until_offset_days 14
@susbscription_accept_traffic_until_offset_days 30

schema "users" do
field :email, :string
field :password_hash
Expand All @@ -30,35 +27,30 @@ defmodule Plausible.Auth.User do
field :password_confirmation, :string, virtual: true
field :name, :string
field :last_seen, :naive_datetime
field :trial_expiry_date, :date
field :theme, Ecto.Enum, values: [:system, :light, :dark]
field :email_verified, :boolean
field :previous_email, :string
field :accept_traffic_until, :date

# Field for purely informational purposes in CRM context
field :notes, :string

# A field only used as a manual override - allow subscribing
# to any plan, even when exceeding its pageview limit
field :allow_next_upgrade_override, :boolean, default: false
# Fields used only by CRM for mapping to the ones in the owned team
field :trial_expiry_date, :date, virtual: true
field :allow_next_upgrade_override, :boolean, virtual: true
field :accept_traffic_until, :date, virtual: true

# Fields for TOTP authentication. See `Plausible.Auth.TOTP`.
field :totp_enabled, :boolean, default: false
field :totp_secret, Plausible.Auth.TOTP.EncryptedBinary
field :totp_token, :string
field :totp_last_used_at, :naive_datetime

embeds_one :grace_period, Plausible.Auth.GracePeriod, on_replace: :update

has_many :sessions, Plausible.Auth.UserSession
has_many :site_memberships, Plausible.Site.Membership
has_many :team_memberships, Plausible.Teams.Membership
has_many :sites, through: [:site_memberships, :site]
has_many :api_keys, Plausible.Auth.ApiKey
has_one :google_auth, Plausible.Site.GoogleAuth
has_one :subscription, Plausible.Billing.Subscription
has_one :enterprise_plan, Plausible.Billing.EnterprisePlan
has_one :owner_membership, Plausible.Teams.Membership, where: [role: :owner]
has_one :my_team, through: [:owner_membership, :team]

timestamps()
end
Expand All @@ -71,7 +63,6 @@ defmodule Plausible.Auth.User do
|> validate_confirmation(:password, required: true)
|> validate_password_strength()
|> hash_password()
|> start_trial()
|> set_email_verification_status()
|> unique_constraint(:email)
end
Expand Down Expand Up @@ -127,30 +118,15 @@ defmodule Plausible.Auth.User do
:name,
:email_verified,
:theme,
:notes,
:trial_expiry_date,
:allow_next_upgrade_override,
:accept_traffic_until,
:notes
:accept_traffic_until
])
|> validate_required([:email, :name, :email_verified])
|> maybe_bump_accept_traffic_until()
|> unique_constraint(:email)
end

defp maybe_bump_accept_traffic_until(changeset) do
expiry_change = get_change(changeset, :trial_expiry_date)

if expiry_change do
put_change(
changeset,
:accept_traffic_until,
Date.add(expiry_change, @trial_accept_traffic_until_offset_days)
)
else
changeset
end
end

def set_password(user, password) do
user
|> cast(%{password: password}, [:password])
Expand Down Expand Up @@ -179,23 +155,6 @@ defmodule Plausible.Auth.User do

def hash_password(changeset), do: changeset

def remove_trial_expiry(user) do
change(user, trial_expiry_date: nil)
end

def start_trial(user) do
trial_expiry = trial_expiry()

change(user,
trial_expiry_date: trial_expiry,
accept_traffic_until: Date.add(trial_expiry, @trial_accept_traffic_until_offset_days)
)
end

def end_trial(user) do
change(user, trial_expiry_date: Date.utc_today() |> Date.shift(day: -1))
end

def password_strength(changeset) do
case get_field(changeset, :password) do
nil ->
Expand Down Expand Up @@ -237,11 +196,6 @@ defmodule Plausible.Auth.User do
Path.join(PlausibleWeb.Endpoint.url(), ["avatar/", hash])
end

def trial_accept_traffic_until_offset_days(), do: @trial_accept_traffic_until_offset_days

def subscription_accept_traffic_until_offset_days(),
do: @susbscription_accept_traffic_until_offset_days

defp validate_email_changed(changeset) do
if !get_change(changeset, :email) && !changeset.errors[:email] do
add_error(changeset, :email, "can't be the same", validation: :different_email)
Expand Down Expand Up @@ -297,14 +251,6 @@ defmodule Plausible.Auth.User do
|> Enum.uniq()
end

defp trial_expiry() do
on_ee do
Date.utc_today() |> Date.shift(day: 30)
else
Date.utc_today() |> Date.shift(year: 100)
end
end

defp set_email_verification_status(user) do
on_ee do
change(user, email_verified: false)
Expand Down
Loading
Loading