From 7c6f2beb2c67d5cc48c20467390bd7b1bcab2714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20Caba=C3=A7o?= Date: Wed, 6 Nov 2024 11:08:49 +0000 Subject: [PATCH] fix: Add endpoint for realtime configuration management (#1188) --- lib/realtime/api/tenant.ex | 14 +++++++++ lib/realtime/tenants.ex | 20 +++++++++++++ .../controllers/tenant_controller.ex | 16 ++++++++++ lib/realtime_web/router.ex | 1 + mix.exs | 2 +- ...add_private_only_flag_column_to_tenant.exs | 9 ++++++ test/realtime/tenants_test.exs | 16 ++++++++++ .../controllers/tenant_controller_test.exs | 29 +++++++++++++++++++ 8 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 priv/repo/migrations/20241106103258_add_private_only_flag_column_to_tenant.exs diff --git a/lib/realtime/api/tenant.ex b/lib/realtime/api/tenant.ex index 99e3b2e38..a78d36856 100644 --- a/lib/realtime/api/tenant.ex +++ b/lib/realtime/api/tenant.ex @@ -26,6 +26,7 @@ defmodule Realtime.Api.Tenant do field(:events_per_second_rolling, :float, virtual: true) field(:events_per_second_now, :integer, virtual: true) field(:notify_private_alpha, :boolean, default: false) + field(:private_only, :boolean, default: false) has_many(:extensions, Realtime.Api.Extensions, foreign_key: :tenant_external_id, @@ -102,4 +103,17 @@ defmodule Realtime.Api.Tenant do def encrypt_jwt_secret(changeset) do update_change(changeset, :jwt_secret, &Crypto.encrypt!/1) end + + def management_changeset(tenant, attrs) do + cast(tenant, attrs, [ + :max_concurrent_users, + :max_events_per_second, + :max_bytes_per_second, + :max_channels_per_client, + :max_joins_per_second, + :suspend, + :notify_private_alpha, + :private_only + ]) + end end diff --git a/lib/realtime/tenants.ex b/lib/realtime/tenants.ex index ee4f33469..1e6560222 100644 --- a/lib/realtime/tenants.ex +++ b/lib/realtime/tenants.ex @@ -267,6 +267,26 @@ defmodule Realtime.Tenants do |> tap(fn _ -> broadcast_operation_event(:unsuspend_tenant, external_id) end) end + @doc """ + Changes only information regading user managent: + :max_concurrent_users + :max_events_per_second + :max_bytes_per_second + :max_channels_per_client + :max_joins_per_second + :suspend + :notify_private_alpha + :private_only + """ + @spec update_management(String.t(), map()) :: {:ok, Tenant.t()} | {:error, term()} + def update_management(tenant_id, attrs) do + tenant_id + |> Cache.get_tenant_by_external_id() + |> Tenant.management_changeset(attrs) + |> Repo.update!() + |> tap(fn _ -> Cache.distributed_invalidate_tenant_cache(tenant_id) end) + end + defp broadcast_operation_event(action, external_id) do Phoenix.PubSub.broadcast!( Realtime.PubSub, diff --git a/lib/realtime_web/controllers/tenant_controller.ex b/lib/realtime_web/controllers/tenant_controller.ex index 3870a4b3b..334d15801 100644 --- a/lib/realtime_web/controllers/tenant_controller.ex +++ b/lib/realtime_web/controllers/tenant_controller.ex @@ -283,4 +283,20 @@ defmodule RealtimeWeb.TenantController do |> render("not_found.json", tenant: nil) end end + + def patch(conn, %{"tenant_id" => tenant_id} = attrs) do + Logger.metadata(external_id: tenant_id, project: tenant_id) + + case Tenants.update_management(tenant_id, attrs) do + {:ok, %Tenant{} = tenant} -> + render(conn, "show.json", tenant: tenant) + + {:error, :tenant_not_found} -> + Helpers.log_error("TenantNotFound", "Tenant not found") + + conn + |> put_status(404) + |> render("not_found.json", tenant: nil) + end + end end diff --git a/lib/realtime_web/router.ex b/lib/realtime_web/router.ex index 54b642240..ae0094d26 100644 --- a/lib/realtime_web/router.ex +++ b/lib/realtime_web/router.ex @@ -90,6 +90,7 @@ defmodule RealtimeWeb.Router do pipe_through(:api) resources("/tenants", TenantController, param: "tenant_id", except: [:edit, :new]) + patch("/tenants/:tenant_id", TenantController, :patch) post("/tenants/:tenant_id/reload", TenantController, :reload) get("/tenants/:tenant_id/health", TenantController, :health) end diff --git a/mix.exs b/mix.exs index b799a7052..d3ef39a9b 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Realtime.MixProject do def project do [ app: :realtime, - version: "2.33.19", + version: "2.33.20", elixir: "~> 1.16.0", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/priv/repo/migrations/20241106103258_add_private_only_flag_column_to_tenant.exs b/priv/repo/migrations/20241106103258_add_private_only_flag_column_to_tenant.exs new file mode 100644 index 000000000..1efc072ef --- /dev/null +++ b/priv/repo/migrations/20241106103258_add_private_only_flag_column_to_tenant.exs @@ -0,0 +1,9 @@ +defmodule Realtime.Repo.Migrations.AddPrivateOnlyFlagColumnToTenant do + use Ecto.Migration + + def change do + alter table(:tenants) do + add(:private_only, :boolean, default: false, null: false) + end + end +end diff --git a/test/realtime/tenants_test.exs b/test/realtime/tenants_test.exs index ef4d30b45..50222606b 100644 --- a/test/realtime/tenants_test.exs +++ b/test/realtime/tenants_test.exs @@ -70,4 +70,20 @@ defmodule Realtime.TenantsTest do assert_receive {:unsuspend_tenant, ^external_id}, 1000 end end + + describe "update_management/2" do + setup do + tenant = tenant_fixture() + topic = "realtime:operations:invalidate_cache" + Phoenix.PubSub.subscribe(Realtime.PubSub, topic) + %{topic: topic, tenant: tenant} + end + + test "sets private_only flag to true and invalidates cache" do + %{external_id: external_id} = tenant_fixture(%{private_only: false}) + tenant = Tenants.update_management(external_id, %{private_only: true}) + assert tenant.private_only == true + assert_receive {:invalidate_cache, ^external_id}, 1000 + end + end end diff --git a/test/realtime_web/controllers/tenant_controller_test.exs b/test/realtime_web/controllers/tenant_controller_test.exs index 3399e7512..09da9a527 100644 --- a/test/realtime_web/controllers/tenant_controller_test.exs +++ b/test/realtime_web/controllers/tenant_controller_test.exs @@ -284,6 +284,35 @@ defmodule RealtimeWeb.TenantControllerTest do end end + describe "update tenant management information" do + setup [:create_tenant] + + test "changes management information and returns updated values", %{ + conn: conn, + tenant: tenant + } do + with_mock JwtVerification, verify: fn _token, _secret, _jwks -> {:ok, %{}} end do + attrs = %{ + "max_concurrent_users" => 300, + "max_channels_per_client" => 150, + "max_events_per_second" => 250, + "max_joins_per_second" => 50, + "private_only" => true + } + + conn = patch(conn, Routes.tenant_path(conn, :patch, tenant.external_id, tenant: attrs)) + data = json_response(conn, 200)["data"] + + tenant = Tenants.Cache.get_tenant_by_external_id(tenant.external_id) + assert tenant.max_concurrent_users == 300 + assert tenant.max_channels_per_client == 150 + assert tenant.max_events_per_second == 250 + assert tenant.max_joins_per_second == 50 + assert tenant.private_only == false + end + end + end + defp create_tenant(_) do %{tenant: tenant_fixture()} end