2021-02-12 11:17:53 +03:00
defmodule Plausible.Workers.CheckUsage do
use Plausible.Repo
use Oban.Worker , queue : :check_usage
2023-10-10 20:35:17 +03:00
require Plausible.Billing.Subscription.Status
2023-11-30 15:30:04 +03:00
alias Plausible.Billing . { Subscription , Quota }
alias Plausible.Auth.User
2021-02-12 11:17:53 +03:00
defmacro yesterday ( ) do
quote do
fragment ( " now() - INTERVAL '1 day' " )
end
end
defmacro last_day_of_month ( day ) do
quote do
fragment (
" (date_trunc('month', ?::date) + interval '1 month' - interval '1 day')::date " ,
unquote ( day )
)
end
end
defmacro day_of_month ( date ) do
quote do
fragment ( " EXTRACT(day from ?::date) " , unquote ( date ) )
end
end
defmacro least ( left , right ) do
quote do
fragment ( " least(?, ?) " , unquote ( left ) , unquote ( right ) )
end
end
@impl Oban.Worker
2023-11-30 15:30:04 +03:00
def perform ( _job , quota_mod \\ Quota , today \\ Timex . today ( ) ) do
2021-03-29 14:53:34 +03:00
yesterday = today |> Timex . shift ( days : - 1 )
2021-02-12 11:17:53 +03:00
active_subscribers =
Repo . all (
2023-11-30 15:30:04 +03:00
from ( u in User ,
2021-02-12 11:17:53 +03:00
join : s in Plausible.Billing.Subscription ,
on : s . user_id == u . id ,
2021-10-20 17:49:11 +03:00
left_join : ep in Plausible.Billing.EnterprisePlan ,
on : ep . user_id == u . id ,
2021-12-02 12:42:34 +03:00
where : is_nil ( u . grace_period ) ,
2023-10-10 20:35:17 +03:00
where : s . status == ^ Subscription.Status . active ( ) ,
2021-03-01 17:51:57 +03:00
where : not is_nil ( s . last_bill_date ) ,
2021-02-12 11:17:53 +03:00
# Accounts for situations like last_bill_date==2021-01-31 AND today==2021-03-01. Since February never reaches the 31st day, the account is checked on 2021-03-01.
where :
least ( day_of_month ( s . last_bill_date ) , day_of_month ( last_day_of_month ( ^ yesterday ) ) ) ==
day_of_month ( ^ yesterday ) ,
2021-10-20 17:49:11 +03:00
preload : [ subscription : s , enterprise_plan : ep ]
2023-08-24 20:22:49 +03:00
)
2021-02-12 11:17:53 +03:00
)
for subscriber <- active_subscribers do
2021-10-22 12:26:07 +03:00
if subscriber . enterprise_plan do
2023-11-30 15:30:04 +03:00
check_enterprise_subscriber ( subscriber , quota_mod )
2021-10-22 12:26:07 +03:00
else
2023-11-30 15:30:04 +03:00
check_regular_subscriber ( subscriber , quota_mod )
2021-10-22 12:26:07 +03:00
end
end
2021-02-12 11:17:53 +03:00
2021-10-22 12:26:07 +03:00
:ok
end
2021-05-11 11:30:47 +03:00
2023-11-30 15:30:04 +03:00
def check_enterprise_subscriber ( subscriber , quota_mod ) do
pageview_usage = check_pageview_usage ( subscriber , quota_mod )
site_usage = check_site_usage_for_enterprise ( subscriber )
2021-05-10 12:52:42 +03:00
2023-11-30 15:30:04 +03:00
case { pageview_usage , site_usage } do
2023-11-06 17:01:55 +03:00
{ { :below_limit , _ } , { :below_limit , _ } } ->
2021-10-22 12:26:07 +03:00
nil
2021-10-20 17:49:11 +03:00
2023-12-06 15:02:22 +03:00
{ { _ , pageview_usage } , { _ , { site_usage , site_allowance } } } ->
PlausibleWeb.Email . enterprise_over_limit_internal_email (
subscriber ,
pageview_usage ,
site_usage ,
site_allowance
)
|> Plausible.Mailer . send ( )
2022-09-20 11:46:28 +03:00
subscriber
2024-01-15 17:59:56 +03:00
|> Plausible.Auth.GracePeriod . start_manual_lock_changeset ( )
2022-09-20 11:46:28 +03:00
|> Repo . update ( )
2021-10-22 12:26:07 +03:00
end
end
2021-10-20 17:49:11 +03:00
2023-11-30 15:30:04 +03:00
defp check_regular_subscriber ( subscriber , quota_mod ) do
case check_pageview_usage ( subscriber , quota_mod ) do
2023-12-06 15:02:22 +03:00
{ :over_limit , pageview_usage } ->
suggested_plan =
Plausible.Billing.Plans . suggest ( subscriber , pageview_usage . last_cycle . total )
2021-10-22 12:26:07 +03:00
2023-12-06 15:02:22 +03:00
PlausibleWeb.Email . over_limit_email ( subscriber , pageview_usage , suggested_plan )
|> Plausible.Mailer . send ( )
2022-09-20 11:46:28 +03:00
subscriber
2024-01-15 17:59:56 +03:00
|> Plausible.Auth.GracePeriod . start_changeset ( )
2022-09-20 11:46:28 +03:00
|> Repo . update ( )
2021-10-22 12:26:07 +03:00
_ ->
nil
2021-02-12 11:17:53 +03:00
end
2021-10-22 12:26:07 +03:00
end
2021-02-12 11:17:53 +03:00
2023-11-30 15:30:04 +03:00
defp check_pageview_usage ( subscriber , quota_mod ) do
usage = quota_mod . monthly_pageview_usage ( subscriber )
2023-12-13 13:47:50 +03:00
limit = Quota . monthly_pageview_limit ( subscriber )
2021-11-29 13:04:02 +03:00
2023-11-30 15:30:04 +03:00
if exceeds_last_two_usage_cycles? ( usage , limit ) do
2023-12-06 15:02:22 +03:00
{ :over_limit , usage }
2023-11-30 15:30:04 +03:00
else
2023-12-06 15:02:22 +03:00
{ :below_limit , usage }
2023-11-30 15:30:04 +03:00
end
end
2023-08-24 20:22:49 +03:00
2023-11-30 15:30:04 +03:00
@spec exceeds_last_two_usage_cycles? ( Quota . monthly_pageview_usage ( ) , non_neg_integer ( ) ) ::
boolean ( )
2023-08-21 16:07:27 +03:00
2023-11-30 15:30:04 +03:00
def exceeds_last_two_usage_cycles? ( usage , limit ) when is_integer ( limit ) do
2024-01-15 17:59:56 +03:00
limit = ceil ( limit * ( 1 + Quota . pageview_allowance_margin ( ) ) )
2023-11-30 15:19:25 +03:00
2023-11-30 15:30:04 +03:00
Enum . all? ( [ usage . last_cycle , usage . penultimate_cycle ] , fn usage ->
not Quota . below_limit? ( usage . total , limit )
end )
2021-10-22 12:26:07 +03:00
end
2023-11-30 15:30:04 +03:00
defp check_site_usage_for_enterprise ( subscriber ) do
2023-08-24 20:22:49 +03:00
limit = subscriber . enterprise_plan . site_limit
2023-11-30 15:30:04 +03:00
usage = Quota . site_usage ( subscriber )
2021-10-22 12:26:07 +03:00
2023-11-30 15:30:04 +03:00
if Quota . below_limit? ( usage , limit ) do
2023-11-06 17:01:55 +03:00
{ :below_limit , { usage , limit } }
2021-10-22 12:26:07 +03:00
else
2023-08-24 20:22:49 +03:00
{ :over_limit , { usage , limit } }
2021-10-22 12:26:07 +03:00
end
2021-02-12 11:17:53 +03:00
end
end