diff --git a/lib/plausible/billing/billing.ex b/lib/plausible/billing/billing.ex
index 245d78f53f7d..031c2d3543fc 100644
--- a/lib/plausible/billing/billing.ex
+++ b/lib/plausible/billing/billing.ex
@@ -89,7 +89,7 @@ defmodule Plausible.Billing do
subscription =
Subscription
|> Repo.get_by(paddle_subscription_id: params["subscription_id"])
- |> Repo.preload(team: :owner)
+ |> Repo.preload(team: :owners)
if subscription do
changeset =
@@ -99,9 +99,11 @@ defmodule Plausible.Billing do
updated = Repo.update!(changeset)
- subscription.team.owner
- |> PlausibleWeb.Email.cancellation_email()
- |> Plausible.Mailer.send()
+ for owner <- subscription.team.owners do
+ owner
+ |> PlausibleWeb.Email.cancellation_email()
+ |> Plausible.Mailer.send()
+ end
updated
end
@@ -212,7 +214,7 @@ defmodule Plausible.Billing do
Teams.Team
|> Repo.get!(subscription.team_id)
|> Teams.with_subscription()
- |> Repo.preload(:owner)
+ |> Repo.preload(:owners)
if subscription.id != team.subscription.id do
Sentry.capture_message("Susbscription ID mismatch",
@@ -236,7 +238,8 @@ defmodule Plausible.Billing do
)
if plan do
- api_keys = from(key in Plausible.Auth.ApiKey, where: key.user_id == ^team.owner.id)
+ owner_ids = Enum.map(team.owners, & &1.id)
+ api_keys = from(key in Plausible.Auth.ApiKey, where: key.user_id in ^owner_ids)
Repo.update_all(api_keys, set: [hourly_request_limit: plan.hourly_api_request_limit])
end
diff --git a/lib/plausible/billing/enterprise_plan_admin.ex b/lib/plausible/billing/enterprise_plan_admin.ex
index e0b56ef5a802..582ddf4731f1 100644
--- a/lib/plausible/billing/enterprise_plan_admin.ex
+++ b/lib/plausible/billing/enterprise_plan_admin.ex
@@ -40,17 +40,17 @@ defmodule Plausible.Billing.EnterprisePlanAdmin do
from(r in query,
inner_join: t in assoc(r, :team),
- inner_join: o in assoc(t, :owner),
+ inner_join: o in assoc(t, :owners),
or_where: ilike(r.paddle_plan_id, ^search_term),
or_where: ilike(o.email, ^search_term) or ilike(o.name, ^search_term),
- preload: [team: {t, owner: o}]
+ preload: [team: {t, owners: o}]
)
end
def custom_show_query(_conn, _schema, query) do
from(ep in query,
inner_join: t in assoc(ep, :team),
- inner_join: o in assoc(t, :owner),
+ inner_join: o in assoc(t, :owners),
select: %{ep | user_id: o.id}
)
end
@@ -68,7 +68,7 @@ defmodule Plausible.Billing.EnterprisePlanAdmin do
]
end
- defp get_user_email(plan), do: plan.team.owner.email
+ defp get_user_email(plan), do: List.first(plan.team.owners).email
def create_changeset(schema, attrs) do
attrs = sanitize_attrs(attrs)
diff --git a/lib/plausible/billing/site_locker.ex b/lib/plausible/billing/site_locker.ex
index 666bb0e04849..df871331e2e4 100644
--- a/lib/plausible/billing/site_locker.ex
+++ b/lib/plausible/billing/site_locker.ex
@@ -26,7 +26,7 @@ defmodule Plausible.Billing.SiteLocker do
Plausible.Teams.end_grace_period(team)
if send_email? do
- team = Repo.preload(team, :owner)
+ team = Repo.preload(team, :owners)
send_grace_period_end_email(team)
end
@@ -64,8 +64,10 @@ defmodule Plausible.Billing.SiteLocker do
usage = Teams.Billing.monthly_pageview_usage(team)
suggested_plan = Plausible.Billing.Plans.suggest(team, usage.last_cycle.total)
- team.owner
- |> PlausibleWeb.Email.dashboard_locked(usage, suggested_plan)
- |> Plausible.Mailer.send()
+ for owner <- team.owners do
+ owner
+ |> PlausibleWeb.Email.dashboard_locked(usage, suggested_plan)
+ |> Plausible.Mailer.send()
+ end
end
end
diff --git a/lib/plausible/site.ex b/lib/plausible/site.ex
index 11d89f4401fa..340031602471 100644
--- a/lib/plausible/site.ex
+++ b/lib/plausible/site.ex
@@ -47,8 +47,8 @@ defmodule Plausible.Site do
has_one :google_auth, GoogleAuth
has_one :weekly_report, Plausible.Site.WeeklyReport
has_one :monthly_report, Plausible.Site.MonthlyReport
- has_one :ownership, through: [:team, :ownership]
- has_one :owner, through: [:team, :owner]
+ has_many :ownerships, through: [:team, :ownerships]
+ has_many :owners, through: [:team, :owners]
# If `from_cache?` is set, the struct might be incomplete - see `Plausible.Site.Cache`.
# Use `Plausible.Repo.reload!(cached_site)` to pre-fill missing fields if
diff --git a/lib/plausible/site/admin.ex b/lib/plausible/site/admin.ex
index 2c89b615930b..67a798968588 100644
--- a/lib/plausible/site/admin.ex
+++ b/lib/plausible/site/admin.ex
@@ -33,9 +33,9 @@ defmodule Plausible.SiteAdmin do
from(r in query,
as: :site,
- inner_join: o in assoc(r, :owner),
+ inner_join: o in assoc(r, :owners),
inner_join: t in assoc(r, :team),
- preload: [owner: o, team: t, guest_memberships: [team_membership: :user]],
+ preload: [owners: o, team: t, guest_memberships: [team_membership: :user]],
or_where: ilike(r.domain, ^search_term),
or_where: ilike(o.email, ^search_term),
or_where: ilike(o.name, ^search_term),
@@ -78,7 +78,7 @@ defmodule Plausible.SiteAdmin do
inserted_at: %{name: "Created at", value: &format_date(&1.inserted_at)},
timezone: nil,
public: nil,
- owner: %{value: &get_owner/1},
+ owners: %{value: &get_owners/1},
other_members: %{value: &get_other_members/1},
limits: %{
value: fn site ->
@@ -186,20 +186,22 @@ defmodule Plausible.SiteAdmin do
Calendar.strftime(date, "%b %-d, %Y")
end
- defp get_owner(site) do
- owner = site.owner
+ defp get_owners(site) do
+ owners = Repo.preload(site, :owners).owners
- if owner do
- escaped_name = Phoenix.HTML.html_escape(owner.name) |> Phoenix.HTML.safe_to_string()
- escaped_email = Phoenix.HTML.html_escape(owner.email) |> Phoenix.HTML.safe_to_string()
+ owners_html =
+ Enum.map(owners, fn owner ->
+ escaped_name = Phoenix.HTML.html_escape(owner.name) |> Phoenix.HTML.safe_to_string()
+ escaped_email = Phoenix.HTML.html_escape(owner.email) |> Phoenix.HTML.safe_to_string()
- {:safe,
- """
- #{escaped_name}
-
- #{escaped_email}
- """}
- end
+ """
+ #{escaped_name}
+
+ #{escaped_email}
+ """
+ end)
+
+ {:safe, Enum.join(owners_html, "
")}
end
defp get_other_members(site) do
diff --git a/lib/plausible/site/memberships/accept_invitation.ex b/lib/plausible/site/memberships/accept_invitation.ex
index 2a569f9f7123..6d64aaf9064a 100644
--- a/lib/plausible/site/memberships/accept_invitation.ex
+++ b/lib/plausible/site/memberships/accept_invitation.ex
@@ -80,9 +80,9 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
:ok <- check_can_transfer_site(new_team, new_owner),
:ok <- Teams.Invitations.ensure_can_take_ownership(site, new_team),
:ok <- Teams.Invitations.transfer_site(site, new_team) do
- site = site |> Repo.reload!() |> Repo.preload(ownership: :user)
+ site = site |> Repo.reload!() |> Repo.preload(ownerships: :user)
- {:ok, site.ownership}
+ {:ok, site.ownerships}
end
end
@@ -96,9 +96,9 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
:ok <- Teams.Invitations.accept_site_transfer(site_transfer, new_team) do
Teams.Invitations.send_transfer_accepted_email(site_transfer)
- site = site |> Repo.reload!() |> Repo.preload(ownership: :user)
+ site = site |> Repo.reload!() |> Repo.preload(ownerships: :user)
- {:ok, %{team: new_team, team_membership: site.ownership, site: site}}
+ {:ok, %{team: new_team, team_memberships: site.ownerships, site: site}}
end
end
diff --git a/lib/plausible/site/memberships/create_invitation.ex b/lib/plausible/site/memberships/create_invitation.ex
index 418085ba69f8..4d31af79de6b 100644
--- a/lib/plausible/site/memberships/create_invitation.ex
+++ b/lib/plausible/site/memberships/create_invitation.ex
@@ -50,7 +50,7 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
end
defp do_invite(site, inviter, invitee_email, role, opts \\ []) do
- with site <- Repo.preload(site, [:owner, :team]),
+ with site <- Repo.preload(site, [:owners, :team]),
:ok <-
Teams.Invitations.check_invitation_permissions(
site,
diff --git a/lib/plausible/teams.ex b/lib/plausible/teams.ex
index 27368b42cddd..0b7a8b50097b 100644
--- a/lib/plausible/teams.ex
+++ b/lib/plausible/teams.ex
@@ -36,15 +36,6 @@ defmodule Plausible.Teams do
Repo.get_by!(Teams.Team, identifier: team_identifier)
end
- @spec get_owner(Teams.Team.t()) ::
- {:ok, Auth.User.t()} | {:error, :no_owner | :multiple_owners}
- def get_owner(team) do
- case Repo.preload(team, :owner).owner do
- nil -> {:error, :no_owner}
- owner_user -> {:ok, owner_user}
- end
- end
-
@spec on_trial?(Teams.Team.t() | nil) :: boolean()
on_ee do
def on_trial?(nil), do: false
@@ -264,7 +255,7 @@ defmodule Plausible.Teams do
end
def setup_team(team, candidates) do
- inviter = Repo.preload(team, :owner).owner
+ [inviter | _] = Repo.preload(team, :owners).owners
setup_team_fn = fn {{email, _name}, role} ->
case Teams.Invitations.InviteToTeam.invite(team, inviter, email, role, send_email?: false) do
diff --git a/lib/plausible/teams/billing.ex b/lib/plausible/teams/billing.ex
index 44cdb9227424..ba356601d498 100644
--- a/lib/plausible/teams/billing.ex
+++ b/lib/plausible/teams/billing.ex
@@ -382,7 +382,7 @@ defmodule Plausible.Teams.Billing do
def team_member_usage(nil, _), do: 0
def team_member_usage(team, opts) do
- {:ok, owner} = Teams.get_owner(team)
+ [owner | _] = Repo.preload(team, :owners).owners
exclude_emails = Keyword.get(opts, :exclude_emails, []) ++ [owner.email]
pending_site_ids = Keyword.get(opts, :pending_ownership_site_ids, [])
diff --git a/lib/plausible/teams/invitations.ex b/lib/plausible/teams/invitations.ex
index 10c8b4a3c343..96de6c822362 100644
--- a/lib/plausible/teams/invitations.ex
+++ b/lib/plausible/teams/invitations.ex
@@ -324,7 +324,7 @@ defmodule Plausible.Teams.Invitations do
site =
Repo.preload(site, [
:team,
- :owner,
+ :owners,
guest_memberships: [team_membership: :user],
guest_invitations: [team_invitation: :inviter]
])
@@ -381,19 +381,21 @@ defmodule Plausible.Teams.Invitations do
Repo.delete_all(from gm in Teams.GuestMembership, where: gm.id in ^old_guest_ids)
:ok = Teams.Memberships.prune_guests(prior_team)
- {:ok, prior_owner} = Teams.get_owner(prior_team)
+ prior_owners = Repo.preload(prior_team, :owners).owners
- {:ok, prior_owner_team_membership} = create_team_membership(team, :guest, prior_owner, now)
+ for prior_owner <- prior_owners do
+ {:ok, prior_owner_team_membership} = create_team_membership(team, :guest, prior_owner, now)
- if prior_owner_team_membership.role == :guest do
- {:ok, _} =
- prior_owner_team_membership
- |> Teams.GuestMembership.changeset(site, :editor)
- |> Repo.insert(
- on_conflict: [set: [updated_at: now, role: :editor]],
- conflict_target: [:team_membership_id, :site_id],
- returning: true
- )
+ if prior_owner_team_membership.role == :guest do
+ {:ok, _} =
+ prior_owner_team_membership
+ |> Teams.GuestMembership.changeset(site, :editor)
+ |> Repo.insert(
+ on_conflict: [set: [updated_at: now, role: :editor]],
+ conflict_target: [:team_membership_id, :site_id],
+ returning: true
+ )
+ end
end
on_ee do
diff --git a/lib/plausible/teams/invitations/invite_to_team.ex b/lib/plausible/teams/invitations/invite_to_team.ex
index f8ce8eddb173..ea2bfe277a2e 100644
--- a/lib/plausible/teams/invitations/invite_to_team.ex
+++ b/lib/plausible/teams/invitations/invite_to_team.ex
@@ -12,7 +12,7 @@ defmodule Plausible.Teams.Invitations.InviteToTeam do
def invite(team, inviter, invitee_email, role, opts \\ [])
def invite(team, inviter, invitee_email, role, opts) when role in @valid_roles do
- with team <- Repo.preload(team, [:owner]),
+ with team <- Repo.preload(team, [:owners]),
:ok <-
Teams.Invitations.check_invitation_permissions(
team,
diff --git a/lib/plausible/teams/team.ex b/lib/plausible/teams/team.ex
index 6871152a3199..ece39d1e440f 100644
--- a/lib/plausible/teams/team.ex
+++ b/lib/plausible/teams/team.ex
@@ -37,8 +37,6 @@ defmodule Plausible.Teams.Team do
has_one :subscription, Plausible.Billing.Subscription
has_one :enterprise_plan, Plausible.Billing.EnterprisePlan
- has_one :ownership, Plausible.Teams.Membership, where: [role: :owner]
- has_one :owner, through: [:ownership, :user]
has_many :ownerships, Plausible.Teams.Membership, where: [role: :owner]
has_many :owners, through: [:ownerships, :user]
diff --git a/lib/plausible_web/controllers/admin_controller.ex b/lib/plausible_web/controllers/admin_controller.ex
index 69c6ef5f1cbd..e7424d543db3 100644
--- a/lib/plausible_web/controllers/admin_controller.ex
+++ b/lib/plausible_web/controllers/admin_controller.ex
@@ -15,7 +15,7 @@ defmodule PlausibleWeb.AdminController do
{:ok, team} ->
team
|> Teams.with_subscription()
- |> Plausible.Repo.preload(:owner)
+ |> Plausible.Repo.preload(:owners)
{:error, :no_team} ->
nil
@@ -177,7 +177,7 @@ defmodule PlausibleWeb.AdminController do
sites_link =
Routes.kaffy_resource_url(PlausibleWeb.Endpoint, :index, :sites, :site,
- custom_search: team.owner.email
+ custom_search: List.first(team.owners).email
)
"""
diff --git a/lib/plausible_web/controllers/api/external_query_api_controller.ex b/lib/plausible_web/controllers/api/external_query_api_controller.ex
index f64414ee7ef0..7fc17e0e64ee 100644
--- a/lib/plausible_web/controllers/api/external_query_api_controller.ex
+++ b/lib/plausible_web/controllers/api/external_query_api_controller.ex
@@ -7,7 +7,7 @@ defmodule PlausibleWeb.Api.ExternalQueryApiController do
alias Plausible.Stats.Query
def query(conn, params) do
- site = Repo.preload(conn.assigns.site, :owner)
+ site = Repo.preload(conn.assigns.site, :owners)
case Query.build(site, conn.assigns.schema_type, params, debug_metadata(conn)) do
{:ok, query} ->
diff --git a/lib/plausible_web/controllers/api/external_stats_controller.ex b/lib/plausible_web/controllers/api/external_stats_controller.ex
index a585a71bd215..82b5c976d982 100644
--- a/lib/plausible_web/controllers/api/external_stats_controller.ex
+++ b/lib/plausible_web/controllers/api/external_stats_controller.ex
@@ -10,7 +10,7 @@ defmodule PlausibleWeb.Api.ExternalStatsController do
end
def aggregate(conn, params) do
- site = Repo.preload(conn.assigns.site, :owner)
+ site = Repo.preload(conn.assigns.site, :owners)
params = Map.put(params, "property", nil)
@@ -38,7 +38,7 @@ defmodule PlausibleWeb.Api.ExternalStatsController do
end
def breakdown(conn, params) do
- site = Repo.preload(conn.assigns.site, :owner)
+ site = Repo.preload(conn.assigns.site, :owners)
with :ok <- validate_period(params),
:ok <- validate_date(params),
@@ -246,7 +246,7 @@ defmodule PlausibleWeb.Api.ExternalStatsController do
defp event_only_property?(_), do: false
def timeseries(conn, params) do
- site = Repo.preload(conn.assigns.site, :owner)
+ site = Repo.preload(conn.assigns.site, :owners)
params = Map.put(params, "property", nil)
diff --git a/lib/plausible_web/controllers/site/membership_controller.ex b/lib/plausible_web/controllers/site/membership_controller.ex
index 9476373128dd..af55049aeae4 100644
--- a/lib/plausible_web/controllers/site/membership_controller.ex
+++ b/lib/plausible_web/controllers/site/membership_controller.ex
@@ -27,7 +27,7 @@ defmodule PlausibleWeb.Site.MembershipController do
site =
conn.assigns.current_user
|> Plausible.Sites.get_for_user!(conn.assigns.site.domain)
- |> Plausible.Repo.preload(:owner)
+ |> Plausible.Repo.preload(:owners)
limit = Plausible.Teams.Billing.team_member_limit(site.team)
usage = Plausible.Teams.Billing.team_member_usage(site.team)
@@ -48,7 +48,7 @@ defmodule PlausibleWeb.Site.MembershipController do
site =
Plausible.Sites.get_for_user!(conn.assigns.current_user, site_domain)
- |> Plausible.Repo.preload(:owner)
+ |> Plausible.Repo.preload(:owners)
case Memberships.create_invitation(site, conn.assigns.current_user, email, role) do
{:ok, invitation} ->
diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex
index 8d5dc4b8cbb3..e7a5c8fa670e 100644
--- a/lib/plausible_web/controllers/stats_controller.ex
+++ b/lib/plausible_web/controllers/stats_controller.ex
@@ -51,7 +51,7 @@ defmodule PlausibleWeb.StatsController do
plug(PlausibleWeb.Plugs.AuthorizeSiteAccess when action in [:stats, :csv_export])
def stats(%{assigns: %{site: site}} = conn, _params) do
- site = Plausible.Repo.preload(site, :owner)
+ site = Plausible.Repo.preload(site, :owners)
current_user = conn.assigns[:current_user]
stats_start_date = Plausible.Sites.stats_start_date(site)
can_see_stats? = not Sites.locked?(site) or conn.assigns[:site_role] == :super_admin
@@ -87,7 +87,7 @@ defmodule PlausibleWeb.StatsController do
redirect(conn, external: Routes.site_path(conn, :verification, site.domain))
Sites.locked?(site) ->
- site = Plausible.Repo.preload(site, :owner)
+ site = Plausible.Repo.preload(site, :owners)
render(conn, "site_locked.html", site: site, dogfood_page_path: dogfood_page_path)
end
end
@@ -112,7 +112,7 @@ defmodule PlausibleWeb.StatsController do
"""
def csv_export(conn, params) do
if is_nil(params["interval"]) or Plausible.Stats.Interval.valid?(params["interval"]) do
- site = Plausible.Repo.preload(conn.assigns.site, :owner)
+ site = Plausible.Repo.preload(conn.assigns.site, :owners)
query = Query.from(site, params, debug_metadata(conn))
date_range = Query.date_range(query)
@@ -347,7 +347,7 @@ defmodule PlausibleWeb.StatsController do
cond do
!shared_link.site.locked ->
current_user = conn.assigns[:current_user]
- shared_link = Plausible.Repo.preload(shared_link, site: :owner)
+ shared_link = Plausible.Repo.preload(shared_link, site: :owners)
stats_start_date = Plausible.Sites.stats_start_date(shared_link.site)
scroll_depth_visible? =
@@ -378,10 +378,10 @@ defmodule PlausibleWeb.StatsController do
)
Sites.locked?(shared_link.site) ->
- owner = Plausible.Repo.preload(shared_link.site, :owner)
+ owners = Plausible.Repo.preload(shared_link.site, :owners)
render(conn, "site_locked.html",
- owner: owner,
+ owners: owners,
site: shared_link.site,
dogfood_page_path: "/share/:dashboard"
)
diff --git a/lib/plausible_web/email.ex b/lib/plausible_web/email.ex
index 5623c3c3b191..326b36749a8f 100644
--- a/lib/plausible_web/email.ex
+++ b/lib/plausible_web/email.ex
@@ -190,22 +190,22 @@ defmodule PlausibleWeb.Email do
})
end
- def yearly_renewal_notification(team) do
+ def yearly_renewal_notification(team, owner) do
date = Calendar.strftime(team.subscription.next_bill_date, "%B %-d, %Y")
priority_email()
- |> to(team.owner)
+ |> to(owner)
|> tag("yearly-renewal")
|> subject("Your Plausible subscription is up for renewal")
|> render("yearly_renewal_notification.html", %{
- user: team.owner,
+ user: owner,
date: date,
next_bill_amount: team.subscription.next_bill_amount,
currency: team.subscription.currency_code
})
end
- def yearly_expiration_notification(team) do
+ def yearly_expiration_notification(team, owner) do
next_bill_date = Calendar.strftime(team.subscription.next_bill_date, "%B %-d, %Y")
accept_traffic_until =
@@ -214,11 +214,11 @@ defmodule PlausibleWeb.Email do
|> Calendar.strftime("%B %-d, %Y")
priority_email()
- |> to(team.owner)
+ |> to(owner)
|> tag("yearly-expiration")
|> subject("Your Plausible subscription is about to expire")
|> render("yearly_expiration_notification.html", %{
- user: team.owner,
+ user: owner,
next_bill_date: next_bill_date,
accept_traffic_until: accept_traffic_until
})
diff --git a/lib/plausible_web/live/goal_settings/form.ex b/lib/plausible_web/live/goal_settings/form.ex
index 669ceee695e8..64db8656a96a 100644
--- a/lib/plausible_web/live/goal_settings/form.ex
+++ b/lib/plausible_web/live/goal_settings/form.ex
@@ -9,7 +9,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
alias Plausible.Repo
def update(assigns, socket) do
- site = Repo.preload(assigns.site, [:team, :owner])
+ site = Repo.preload(assigns.site, [:team, :owners])
has_access_to_revenue_goals? =
Plausible.Billing.Feature.RevenueGoals.check_availability(site.team) == :ok
@@ -283,7 +283,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
~H"""
This dashboard is currently locked and cannot be accessed. The site owner - {@site.owner.email} + {List.first(@site.owners).email} must upgrade their subscription plan in order to unlock the stats.
Want to pay for this site with the account you're logged in with?
- Contact {@site.owner.email} and ask them to + Contact {List.first(@site.owners).email} and ask them to <.styled_link href="https://plausible.io/docs/transfer-ownership" new_tab={true}> transfer the ownership diff --git a/lib/workers/accept_traffic_until_notification.ex b/lib/workers/accept_traffic_until_notification.ex index 585c9c2aff56..ae28beae07ad 100644 --- a/lib/workers/accept_traffic_until_notification.ex +++ b/lib/workers/accept_traffic_until_notification.ex @@ -26,14 +26,14 @@ defmodule Plausible.Workers.AcceptTrafficUntil do # send at most one notification per user, per day sent_today_query = from s in "sent_accept_traffic_until_notifications", - where: s.user_id == parent_as(:user).id and s.sent_on == ^today, + where: s.user_id == parent_as(:users).id and s.sent_on == ^today, select: true notifications = Repo.all( from t in Plausible.Teams.Team, - inner_join: u in assoc(t, :owner), - as: :user, + inner_join: u in assoc(t, :owners), + as: :users, inner_join: s in assoc(t, :sites), where: t.accept_traffic_until == ^tomorrow or t.accept_traffic_until == ^next_week, where: not exists(sent_today_query), diff --git a/lib/workers/check_usage.ex b/lib/workers/check_usage.ex index b9fddcfab0be..a6e72e12cc08 100644 --- a/lib/workers/check_usage.ex +++ b/lib/workers/check_usage.ex @@ -40,7 +40,7 @@ defmodule Plausible.Workers.CheckUsage do Repo.all( from(t in Teams.Team, as: :team, - inner_join: o in assoc(t, :owner), + inner_join: o in assoc(t, :owners), inner_lateral_join: s in subquery(Teams.last_subscription_join_query()), on: true, left_join: ep in Plausible.Billing.EnterprisePlan, @@ -58,7 +58,7 @@ defmodule Plausible.Workers.CheckUsage do least(day_of_month(s.last_bill_date), day_of_month(last_day_of_month(^yesterday))) == day_of_month(^yesterday), order_by: t.id, - preload: [subscription: s, enterprise_plan: ep, owner: o] + preload: [subscription: s, enterprise_plan: ep, owners: o] ) ) @@ -110,8 +110,10 @@ defmodule Plausible.Workers.CheckUsage do suggested_plan = Plausible.Billing.Plans.suggest(subscriber, pageview_usage.last_cycle.total) - PlausibleWeb.Email.over_limit_email(subscriber.owner, pageview_usage, suggested_plan) - |> Plausible.Mailer.send() + for owner <- subscriber.owners do + PlausibleWeb.Email.over_limit_email(owner, pageview_usage, suggested_plan) + |> Plausible.Mailer.send() + end Plausible.Teams.start_grace_period(subscriber) @@ -129,13 +131,15 @@ defmodule Plausible.Workers.CheckUsage do nil {{_, pageview_usage}, {_, {site_usage, site_allowance}}} -> - PlausibleWeb.Email.enterprise_over_limit_internal_email( - subscriber.owner, - pageview_usage, - site_usage, - site_allowance - ) - |> Plausible.Mailer.send() + for owner <- subscriber.owners do + PlausibleWeb.Email.enterprise_over_limit_internal_email( + owner, + pageview_usage, + site_usage, + site_allowance + ) + |> Plausible.Mailer.send() + end Plausible.Teams.start_manual_lock_grace_period(subscriber) end diff --git a/lib/workers/notify_annual_renewal.ex b/lib/workers/notify_annual_renewal.ex index e959e22abdb1..aeb0f8385c0b 100644 --- a/lib/workers/notify_annual_renewal.ex +++ b/lib/workers/notify_annual_renewal.ex @@ -25,7 +25,7 @@ defmodule Plausible.Workers.NotifyAnnualRenewal do Repo.all( from t in Teams.Team, as: :team, - inner_join: o in assoc(t, :owner), + inner_join: o in assoc(t, :owners), inner_lateral_join: s in subquery(Teams.last_subscription_join_query()), on: true, left_join: sent in ^sent_notification, @@ -35,29 +35,38 @@ defmodule Plausible.Workers.NotifyAnnualRenewal do where: s.next_bill_date > fragment("now()::date") and s.next_bill_date <= fragment("now()::date + INTERVAL '7 days'"), - preload: [owner: o, subscription: s] + preload: [owners: o, subscription: s] ) for team <- teams do case team.subscription.status do Subscription.Status.active() -> - template = PlausibleWeb.Email.yearly_renewal_notification(team) - Plausible.Mailer.send(template) + for owner <- team.owners do + template = PlausibleWeb.Email.yearly_renewal_notification(team, owner) + Plausible.Mailer.send(template) + end Subscription.Status.deleted() -> - template = PlausibleWeb.Email.yearly_expiration_notification(team) - Plausible.Mailer.send(template) + for owner <- team.owners do + template = PlausibleWeb.Email.yearly_expiration_notification(team, owner) + Plausible.Mailer.send(template) + end _ -> - Sentry.capture_message("Invalid subscription for renewal", team: team, user: team.owner) + Sentry.capture_message("Invalid subscription for renewal", + team: team, + user: List.first(team.owner) + ) end - Repo.insert_all("sent_renewal_notifications", [ - %{ - user_id: team.owner.id, - timestamp: NaiveDateTime.utc_now() - } - ]) + for owner <- team.owners do + Repo.insert_all("sent_renewal_notifications", [ + %{ + user_id: owner.id, + timestamp: NaiveDateTime.utc_now() + } + ]) + end end :ok diff --git a/lib/workers/send_site_setup_emails.ex b/lib/workers/send_site_setup_emails.ex index db34d9366dec..02678022a194 100644 --- a/lib/workers/send_site_setup_emails.ex +++ b/lib/workers/send_site_setup_emails.ex @@ -44,16 +44,16 @@ defmodule Plausible.Workers.SendSiteSetupEmails do on: se.site_id == s.id, where: is_nil(se.id), where: s.inserted_at > fragment("(now() at time zone 'utc') - '72 hours'::interval"), - preload: [:owner, :team] + preload: [:owners, :team] ) for site <- Repo.all(q) do - owner = site.owner + owners = site.owners setup_completed = Plausible.Sites.has_stats?(site) hours_passed = NaiveDateTime.diff(DateTime.utc_now(), site.inserted_at, :hour) if !setup_completed && hours_passed > 47 do - send_setup_help_email(owner, site) + send_setup_help_email(owners, site) end end end @@ -66,7 +66,7 @@ defmodule Plausible.Workers.SendSiteSetupEmails do where: is_nil(se.id), inner_join: t in assoc(s, :team), where: s.inserted_at > fragment("(now() at time zone 'utc') - '72 hours'::interval"), - preload: [:owner, team: t] + preload: [:owners, team: t] ) for site <- Repo.all(q) do @@ -89,8 +89,10 @@ defmodule Plausible.Workers.SendSiteSetupEmails do end defp send_setup_success_email(site) do - PlausibleWeb.Email.site_setup_success(site.owner, site.team, site) - |> Plausible.Mailer.send() + for owner <- site.owners do + PlausibleWeb.Email.site_setup_success(owner, site.team, site) + |> Plausible.Mailer.send() + end Repo.insert_all("setup_success_emails", [ %{ @@ -100,9 +102,11 @@ defmodule Plausible.Workers.SendSiteSetupEmails do ]) end - defp send_setup_help_email(user, site) do - PlausibleWeb.Email.site_setup_help(user, site) - |> Plausible.Mailer.send() + defp send_setup_help_email(users, site) do + for user <- users do + PlausibleWeb.Email.site_setup_help(user, site) + |> Plausible.Mailer.send() + end Repo.insert_all("setup_help_emails", [ %{ diff --git a/test/plausible_web/controllers/stats_controller_test.exs b/test/plausible_web/controllers/stats_controller_test.exs index 5fd9f620a66d..a9a0febfd095 100644 --- a/test/plausible_web/controllers/stats_controller_test.exs +++ b/test/plausible_web/controllers/stats_controller_test.exs @@ -287,8 +287,8 @@ defmodule PlausibleWeb.StatsControllerTest do } do {:ok, site} = Plausible.Props.allow(site, ["author"]) - site = Repo.preload(site, :owner) - subscribe_to_growth_plan(site.owner) + [owner | _] = Repo.preload(site, :owners).owners + subscribe_to_growth_plan(owner) populate_stats(site, [ build(:pageview, "meta.key": ["author"], "meta.value": ["a"]), @@ -315,8 +315,8 @@ defmodule PlausibleWeb.StatsControllerTest do } do {:ok, site} = Plausible.Props.allow(site, ["author"]) - site = Repo.preload(site, :owner) - subscribe_to_growth_plan(site.owner) + [owner | _] = Repo.preload(site, :owners).owners + subscribe_to_growth_plan(owner) populate_stats(site, [ build(:pageview, "meta.key": ["author"], "meta.value": ["a"]) diff --git a/test/plausible_web/plugins/api/controllers/capabilities_test.exs b/test/plausible_web/plugins/api/controllers/capabilities_test.exs index 4c46621ba306..dc32d73d03f9 100644 --- a/test/plausible_web/plugins/api/controllers/capabilities_test.exs +++ b/test/plausible_web/plugins/api/controllers/capabilities_test.exs @@ -88,8 +88,8 @@ defmodule PlausibleWeb.Plugins.API.Controllers.CapabilitiesTest do @tag :ee_only test "growth", %{conn: conn, site: site, token: token} do - site = Plausible.Repo.preload(site, :owner) - subscribe_to_growth_plan(site.owner) + [owner | _] = Plausible.Repo.preload(site, :owners).owners + subscribe_to_growth_plan(owner) resp = conn diff --git a/test/plausible_web/plugins/api/controllers/custom_props_test.exs b/test/plausible_web/plugins/api/controllers/custom_props_test.exs index e7b461e597b5..12589c5dd17e 100644 --- a/test/plausible_web/plugins/api/controllers/custom_props_test.exs +++ b/test/plausible_web/plugins/api/controllers/custom_props_test.exs @@ -42,8 +42,8 @@ defmodule PlausibleWeb.Plugins.API.Controllers.CustomPropsTest do token: token, conn: conn } do - site = Plausible.Repo.preload(site, :owner) - subscribe_to_growth_plan(site.owner) + [owner | _] = Plausible.Repo.preload(site, :owners).owners + subscribe_to_growth_plan(owner) url = Routes.plugins_api_custom_props_url(PlausibleWeb.Endpoint, :enable) @@ -66,8 +66,8 @@ defmodule PlausibleWeb.Plugins.API.Controllers.CustomPropsTest do token: token, conn: conn } do - site = Plausible.Repo.preload(site, :owner) - subscribe_to_growth_plan(site.owner) + [owner | _] = Plausible.Repo.preload(site, :owners).owners + subscribe_to_growth_plan(owner) url = Routes.plugins_api_custom_props_url(PlausibleWeb.Endpoint, :enable) diff --git a/test/plausible_web/plugins/api/controllers/funnels_test.exs b/test/plausible_web/plugins/api/controllers/funnels_test.exs index cba0f6267427..8ed2d40be78e 100644 --- a/test/plausible_web/plugins/api/controllers/funnels_test.exs +++ b/test/plausible_web/plugins/api/controllers/funnels_test.exs @@ -249,8 +249,8 @@ defmodule PlausibleWeb.Plugins.API.Controllers.FunnelsTest do end test "fails for insufficient plan", %{conn: conn, token: token, site: site} do - site = Plausible.Repo.preload(site, :owner) - subscribe_to_growth_plan(site.owner) + [owner | _] = Plausible.Repo.preload(site, :owners).owners + subscribe_to_growth_plan(owner) url = Routes.plugins_api_funnels_url(PlausibleWeb.Endpoint, :create) diff --git a/test/plausible_web/plugins/api/controllers/goals_test.exs b/test/plausible_web/plugins/api/controllers/goals_test.exs index aea1053dd78e..129d374f0409 100644 --- a/test/plausible_web/plugins/api/controllers/goals_test.exs +++ b/test/plausible_web/plugins/api/controllers/goals_test.exs @@ -53,8 +53,8 @@ defmodule PlausibleWeb.Plugins.API.Controllers.GoalsTest do token: token, conn: conn } do - site = Plausible.Repo.preload(site, :owner) - subscribe_to_growth_plan(site.owner) + [owner | _] = Plausible.Repo.preload(site, :owners).owners + subscribe_to_growth_plan(owner) url = Routes.plugins_api_goals_url(PlausibleWeb.Endpoint, :create) @@ -79,8 +79,8 @@ defmodule PlausibleWeb.Plugins.API.Controllers.GoalsTest do token: token, conn: conn } do - site = Plausible.Repo.preload(site, :owner) - subscribe_to_growth_plan(site.owner) + [owner | _] = Plausible.Repo.preload(site, :owners).owners + subscribe_to_growth_plan(owner) url = Routes.plugins_api_goals_url(PlausibleWeb.Endpoint, :create) diff --git a/test/support/teams/test.ex b/test/support/teams/test.ex index 3ac8c6d553c4..13fff5475cef 100644 --- a/test/support/teams/test.ex +++ b/test/support/teams/test.ex @@ -20,9 +20,11 @@ defmodule Plausible.Teams.Test do def new_site(args \\ []) do args = if user = args[:owner] do + {owner, args} = Keyword.pop(args, :owner) {:ok, team} = Teams.get_or_create(user) args + |> Keyword.put(:owners, [owner]) |> Keyword.put(:team, team) else user = new_user() @@ -299,11 +301,13 @@ defmodule Plausible.Teams.Test do end def assert_team_attached(site, team_id \\ nil) do - assert site = %{team: team} = site |> Repo.reload!() |> Repo.preload([:team, :owner]) + assert site = %{team: team} = site |> Repo.reload!() |> Repo.preload([:team, :owners]) - assert membership = assert_team_membership(site.owner, team) + for owner <- site.owners do + assert membership = assert_team_membership(owner, team) - assert membership.team_id == team.id + assert membership.team_id == team.id + end if team_id do assert team.id == team_id