From 8d85c38047fe593dd64b11b61b62f0bf03b9358a Mon Sep 17 00:00:00 2001 From: hq1 Date: Wed, 13 Dec 2023 14:52:32 +0100 Subject: [PATCH] Add `:payment_required` policy to `GateKeeper` (#3628) * Migration: Add `accept_traffic_until` to "sites" * Drop traffic from sites where `accept_traffic_until` is passed --- lib/plausible/site.ex | 1 + lib/plausible/site/cache.ex | 1 + lib/plausible/site/gate_keeper.ex | 22 ++++++++++++---------- test/plausible/ingestion/event_test.exs | 17 +++++++++++++++++ test/plausible/site/cache_test.exs | 7 +++++-- test/plausible/site/gate_keeper_test.exs | 10 ++++++++++ 6 files changed, 46 insertions(+), 12 deletions(-) diff --git a/lib/plausible/site.ex b/lib/plausible/site.ex index c571619ab..8a1a5e360 100644 --- a/lib/plausible/site.ex +++ b/lib/plausible/site.ex @@ -21,6 +21,7 @@ defmodule Plausible.Site do field :conversions_enabled, :boolean, default: true field :props_enabled, :boolean, default: true field :funnels_enabled, :boolean, default: true + field :accept_traffic_until, :naive_datetime field :ingest_rate_limit_scale_seconds, :integer, default: 60 # default is set via changeset/2 diff --git a/lib/plausible/site/cache.ex b/lib/plausible/site/cache.ex index 42cd6c2d4..55df2bf4e 100644 --- a/lib/plausible/site/cache.ex +++ b/lib/plausible/site/cache.ex @@ -56,6 +56,7 @@ defmodule Plausible.Site.Cache do domain_changed_from ingest_rate_limit_scale_seconds ingest_rate_limit_threshold + accept_traffic_until )a @type t() :: Site.t() diff --git a/lib/plausible/site/gate_keeper.ex b/lib/plausible/site/gate_keeper.ex index e543dfcbc..ca3df06d8 100644 --- a/lib/plausible/site/gate_keeper.ex +++ b/lib/plausible/site/gate_keeper.ex @@ -1,5 +1,5 @@ defmodule Plausible.Site.GateKeeper do - @type policy() :: :allow | :not_found | :block | :throttle + @type policy() :: :allow | :not_found | :block | :throttle | :payment_required @policy_for_non_existing_sites :not_found @type t() :: {:allow, Plausible.Site.t()} | {:deny, policy()} @@ -43,16 +43,18 @@ defmodule Plausible.Site.GateKeeper do end defp policy(domain, opts) when is_binary(domain) do - result = - case Cache.get(domain, Keyword.get(opts, :cache_opts, [])) do - nil -> - @policy_for_non_existing_sites - - %Site{} = site -> - check_rate_limit(site, opts) + with from_cache <- Cache.get(domain, Keyword.get(opts, :cache_opts, [])), + site = %Site{accept_traffic_until: accept_traffic_until} <- from_cache do + if not is_nil(accept_traffic_until) and + NaiveDateTime.after?(NaiveDateTime.utc_now(), accept_traffic_until) do + :payment_required + else + check_rate_limit(site, opts) end - - result + else + _ -> + @policy_for_non_existing_sites + end end defp check_rate_limit(%Site{ingest_rate_limit_threshold: nil} = site, _opts) do diff --git a/test/plausible/ingestion/event_test.exs b/test/plausible/ingestion/event_test.exs index 01934b1fd..e4a6854b3 100644 --- a/test/plausible/ingestion/event_test.exs +++ b/test/plausible/ingestion/event_test.exs @@ -100,6 +100,23 @@ defmodule Plausible.Ingestion.EventTest do assert dropped.drop_reason == :throttle end + test "event pipeline drops events for site with accept_trafic_until in the past" do + yesterday = NaiveDateTime.add(NaiveDateTime.utc_now(), -1, :day) + site = insert(:site, ingest_rate_limit_threshold: 1, accept_traffic_until: yesterday) + + payload = %{ + name: "pageview", + url: "http://dummy.site", + d: "#{site.domain}" + } + + conn = build_conn(:post, "/api/events", payload) + assert {:ok, request} = Request.build(conn) + + assert {:ok, %{buffered: [], dropped: [dropped]}} = Event.build_and_buffer(request) + assert dropped.drop_reason == :payment_required + end + @tag :full_build_only test "saves revenue amount" do site = insert(:site) diff --git a/test/plausible/site/cache_test.exs b/test/plausible/site/cache_test.exs index e9616bad0..d081a003f 100644 --- a/test/plausible/site/cache_test.exs +++ b/test/plausible/site/cache_test.exs @@ -34,7 +34,9 @@ defmodule Plausible.Site.CacheTest do ) %{id: first_id} = site1 = insert(:site, domain: "site1.example.com") - _ = insert(:site, domain: "site2.example.com") + + _ = + insert(:site, domain: "site2.example.com", accept_traffic_until: ~N[2021-01-01 00:00:00]) :ok = Cache.refresh_all(cache_name: test) @@ -48,7 +50,8 @@ defmodule Plausible.Site.CacheTest do assert %Site{from_cache?: true} = Cache.get("site2.example.com", force?: true, cache_name: test) - assert %Site{from_cache?: false} = Cache.get("site2.example.com", cache_name: test) + assert %Site{from_cache?: false, accept_traffic_until: ~N[2021-01-01 00:00:00]} = + Cache.get("site2.example.com", cache_name: test) refute Cache.get("site3.example.com", cache_name: test, force?: true) end diff --git a/test/plausible/site/gate_keeper_test.exs b/test/plausible/site/gate_keeper_test.exs index c31285e71..88d314bd1 100644 --- a/test/plausible/site/gate_keeper_test.exs +++ b/test/plausible/site/gate_keeper_test.exs @@ -14,6 +14,15 @@ defmodule Plausible.Site.GateKeeperTest do assert {:deny, :not_found} = GateKeeper.check("example.com", opts) end + test "sites with accepted_traffic_until < now are denied", %{test: test, opts: opts} do + domain = "expired.example.com" + yesterday = NaiveDateTime.utc_now() |> NaiveDateTime.add(-1, :day) + + %{id: _} = add_site_and_refresh_cache(test, domain: domain, accept_traffic_until: yesterday) + + assert {:deny, :payment_required} = GateKeeper.check(domain, opts) + end + test "site from cache with no ingest_rate_limit_threshold is allowed", %{test: test, opts: opts} do domain = "site1.example.com" @@ -92,6 +101,7 @@ defmodule Plausible.Site.GateKeeperTest do defp add_site_and_refresh_cache(cache_name, site_data) do site = insert(:site, site_data) + Cache.refresh_updated_recently(cache_name: cache_name, force?: true) site end