From 35b8577c4c8fec081fa8868b30452883c9991b66 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:16:33 -0500 Subject: [PATCH 01/23] replace page event handlers with global event handlers --- lib/beacon/content.ex | 83 +++++++--------- ...page_event_handler.ex => event_handler.ex} | 13 ++- lib/beacon/content/live_data_assign.ex | 2 +- lib/beacon/content/page.ex | 2 +- lib/beacon/migration.ex | 98 ++++++++++++++++--- lib/beacon/migrations/v002.ex | 41 ++++++++ lib/errors.ex | 2 +- mix.exs | 7 +- test/beacon/content_test.exs | 24 ++--- test/beacon_web/live/page_live_test.exs | 2 +- test/support/fixtures.ex | 16 +-- 11 files changed, 191 insertions(+), 99 deletions(-) rename lib/beacon/content/{page_event_handler.ex => event_handler.ex} (84%) create mode 100644 lib/beacon/migrations/v002.ex diff --git a/lib/beacon/content.ex b/lib/beacon/content.ex index 652d5f21..2d3eed70 100644 --- a/lib/beacon/content.ex +++ b/lib/beacon/content.ex @@ -35,6 +35,7 @@ defmodule Beacon.Content do alias Beacon.Content.ComponentSlot alias Beacon.Content.ComponentSlotAttr alias Beacon.Content.ErrorPage + alias Beacon.Content.EventHandler alias Beacon.Content.Layout alias Beacon.Content.LayoutEvent alias Beacon.Content.LayoutSnapshot @@ -42,7 +43,6 @@ defmodule Beacon.Content do alias Beacon.Content.LiveDataAssign alias Beacon.Content.Page alias Beacon.Content.PageEvent - alias Beacon.Content.PageEventHandler alias Beacon.Content.PageField alias Beacon.Content.PageSnapshot alias Beacon.Content.PageVariant @@ -3521,77 +3521,60 @@ defmodule Beacon.Content do ## Example - iex> change_page_event_handler(page_event_handler, %{name: "form-submit"}) - %Ecto.Changeset{data: %PageEventHandler{}} + iex> change_event_handler(event_handler, %{name: "form-submit"}) + %Ecto.Changeset{data: %EventHandler{}} """ - @doc type: :page_event_handlers - @spec change_page_event_handler(PageEventHandler.t(), map()) :: Changeset.t() - def change_page_event_handler(%PageEventHandler{} = event_handler, attrs \\ %{}) do - PageEventHandler.changeset(event_handler, attrs) + @doc type: :event_handlers + @spec change_event_handler(EventHandler.t(), map()) :: Changeset.t() + def change_event_handler(%EventHandler{} = event_handler, attrs \\ %{}) do + EventHandler.changeset(event_handler, attrs) end @doc """ - Creates a new page event handler and returns the page with updated `:event_handlers` association. + Creates a new event handler. """ - @doc type: :page_event_handlers - @spec create_event_handler_for_page(Page.t(), %{name: binary(), code: binary()}) :: {:ok, Page.t()} | {:error, Changeset.t()} - def create_event_handler_for_page(page, attrs) do + @doc type: :event_handlers + @spec create_event_handler(%{name: binary(), code: binary(), site: Site.t()}) :: + {:ok, EventHandler.t()} | {:error, Changeset.t()} + def create_event_handler(attrs) do changeset = - page - |> Ecto.build_assoc(:event_handlers) - |> PageEventHandler.changeset(attrs) - |> validate_page_event_handler(page) + %EventHandler{} + |> EventHandler.changeset(attrs) + |> validate_event_handler() - transact(repo(page), fn -> - with {:ok, %PageEventHandler{}} <- repo(page).insert(changeset), - %Page{} = page <- repo(page).preload(page, :event_handlers, force: true), - %Page{} = page <- Lifecycle.Page.after_update_page(page) do - {:ok, page} - end - end) + site = Changeset.get_field(changeset, :site) + + repo(site).insert(changeset) end @doc """ - Updates a page event handler and returns the page with updated `:event_handlers` association. + Updates an event handler with the given attrs. """ - @doc type: :page_event_handlers - @spec update_event_handler_for_page(Page.t(), PageEventHandler.t(), map()) :: {:ok, Page.t()} | {:error, Changeset.t()} - def update_event_handler_for_page(page, event_handler, attrs) do - changeset = - event_handler - |> PageEventHandler.changeset(attrs) - |> validate_page_event_handler(page) - - transact(repo(page), fn -> - with {:ok, %PageEventHandler{}} <- repo(page).update(changeset), - %Page{} = page <- repo(page).preload(page, :event_handlers, force: true), - %Page{} = page <- Lifecycle.Page.after_update_page(page) do - {:ok, page} - end - end) + @doc type: :event_handlers + @spec update_event_handler(EventHandler.t(), map()) :: {:ok, EventHandler.t()} | {:error, Changeset.t()} + def update_event_handler(event_handler, attrs) do + event_handler + |> EventHandler.changeset(attrs) + |> validate_event_handler() + |> repo(event_handler).update() end - defp validate_page_event_handler(changeset, page) do + defp validate_event_handler(changeset) do code = Changeset.get_field(changeset, :code) - metadata = %Beacon.Template.LoadMetadata{site: page.site, path: page.path} variable_names = ["socket", "event_params"] imports = ["Phoenix.Socket"] - do_validate_template(changeset, :code, :elixir, code, metadata, variable_names, imports) + do_validate_template(changeset, :code, :elixir, code, nil, variable_names, imports) end @doc """ - Deletes a page event handler and returns the page with updated `:event_handlers` association. + Deletes an event handler. """ - @doc type: :page_event_handlers - @spec delete_event_handler_from_page(Page.t(), PageEventHandler.t()) :: {:ok, Page.t()} | {:error, Changeset.t()} - def delete_event_handler_from_page(page, event_handler) do - with {:ok, %PageEventHandler{}} <- repo(page).delete(event_handler), - %Page{} = page <- repo(page).preload(page, :event_handlers, force: true), - %Page{} = page <- Lifecycle.Page.after_update_page(page) do - {:ok, page} - end + @doc type: :event_handlers + @spec delete_event_handler(EventHandler.t()) :: {:ok, EventHandler.t()} | {:error, Changeset.t()} + def delete_event_handler(event_handler) do + repo(event_handler).delete(event_handler) end # PAGE VARIANTS diff --git a/lib/beacon/content/page_event_handler.ex b/lib/beacon/content/event_handler.ex similarity index 84% rename from lib/beacon/content/page_event_handler.ex rename to lib/beacon/content/event_handler.ex index 5b290fa7..fb4b1be6 100644 --- a/lib/beacon/content/page_event_handler.ex +++ b/lib/beacon/content/event_handler.ex @@ -1,4 +1,4 @@ -defmodule Beacon.Content.PageEventHandler do +defmodule Beacon.Content.EventHandler do @moduledoc """ Beacon's representation of a LiveView [handle_event/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:handle_event/3). @@ -18,30 +18,29 @@ defmodule Beacon.Content.PageEventHandler do import Ecto.Changeset alias Beacon.Content.Page + alias Beacon.Types.Site alias Ecto.UUID @type t :: %__MODULE__{ id: UUID.t(), name: binary(), code: binary(), - page_id: UUID.t(), - page: Page.t(), + site: Site.t(), inserted_at: DateTime.t(), updated_at: DateTime.t() } - schema "beacon_page_event_handlers" do + schema "beacon_event_handlers" do field :name, :string field :code, :string - - belongs_to :page, Page + field :site, Site timestamps() end @doc false def changeset(%__MODULE__{} = event_handler, attrs) do - fields = ~w(name code)a + fields = ~w(name code site)a event_handler |> cast(attrs, fields) diff --git a/lib/beacon/content/live_data_assign.ex b/lib/beacon/content/live_data_assign.ex index e5763d91..004d8409 100644 --- a/lib/beacon/content/live_data_assign.ex +++ b/lib/beacon/content/live_data_assign.ex @@ -1,6 +1,6 @@ defmodule Beacon.Content.LiveDataAssign do @moduledoc """ - Dynamic key/value assigns to be used by `Beacon.Content.Page` templates and updated with `Beacon.Content.PageEventHandler`s. + Dynamic key/value assigns to be used by `Beacon.Content.Page` templates and updated with `Beacon.Content.EventHandler`s. LiveDataAssigns don't exist on their own, but exist as part of a `Beacon.Content.LiveData` struct. diff --git a/lib/beacon/content/page.ex b/lib/beacon/content/page.ex index b4f67919..a5339382 100644 --- a/lib/beacon/content/page.ex +++ b/lib/beacon/content/page.ex @@ -45,7 +45,7 @@ defmodule Beacon.Content.Page do belongs_to :layout, Content.Layout has_many :variants, Content.PageVariant - has_many :event_handlers, Content.PageEventHandler + has_many :event_handlers, Content.EventHandler embeds_many :helpers, Helper diff --git a/lib/beacon/migration.ex b/lib/beacon/migration.ex index 379240bb..2b73546f 100644 --- a/lib/beacon/migration.ex +++ b/lib/beacon/migration.ex @@ -7,10 +7,10 @@ defmodule Beacon.Migration do To install Beacon, you'll need to generate an `Ecto.Migration` that wraps calls to `Beacon.Migration`: ``` - mix ecto.gen.migration create_beacon_tables + $ mix ecto.gen.migration create_beacon_tables ``` - Open the generated migration in your editor and either call or delegate to `up/0` and `down/0`: + Open the generated migration in your editor and either call or delegate to `up/1` and `down/1`: ```elixir defmodule MyApp.Repo.Migrations.CreateBeaconTables do @@ -23,30 +23,100 @@ defmodule Beacon.Migration do Then, run the migrations for your app to create the necessary Beacon tables in your database: ``` - mix ecto.migrate + $ mix ecto.migrate ``` - Note that `up/0` will always execute all migration steps from the initial version to the latest version, - and those migration are idempotent. + By calling `up()` with no arguments, this will execute all migration steps from the initial version to + the latest version. As new versions are released, you may need to repeat this process, by first + generating a new migration: - Check out the [your first site](https://hexdocs.pm/beacon/your-first-site.html) guide for a full example. + ``` + $ mix ecto.gen.migration upgrade_beacon_tables_to_v2 + ``` + + Then in the generated migration, you could simply call `up()` again, because the migrations are + idempotent, but you can be safer and more efficient by specifying the migration version to execute: + + ```elixir + defmodule MyApp.Repo.Migrations.UpgradeBeaconTables do + use Ecto.Migration + def up, do: Beacon.Migration.up(version: 2) + def down, do: Beacon.Migration.down(version: 2) + end + ``` + + Now this migration will update to v2, but if rolled back, will only roll back the v2 changes, + leaving v1 tables in-place. + To see this step within the larger context of installing Beacon, check out the [your first site](your-first-site.html) guide. """ - # TODO: `up/1` should execute all migrations from v001 up to `@latest` - @latest Beacon.Migrations.V001 + @initial_version 1 + @current_version 2 @doc """ - Run the `up` changes for all migrations between the initial version and the current version. + Upgrades Beacon database schemas. + + If a specific version number is provided, Beacon will only upgrade to that version. + Otherwise, it will bring you fully up-to-date with the current version. + + ## Example + + Run all migrations up to the current version: + + Beacon.Migration.up() + + Run migrations up to a specified version: + + Beacon.Migration.down(version: 2) + """ - def up do - @latest.up() + def up(opts \\ []) do + versions_to_run = + case opts[:version] do + nil -> @initial_version..@current_version + version -> @initial_version..version + end + + Enum.each(versions_to_run, fn version -> + padded = String.pad_leading("#{version}", 3, "0") + + [Beacon.Migrations, "v#{padded}"] + |> Module.concat() + |> apply(:up, []) + end) end @doc """ - Run the `down` changes for all migrations between the initial version and the current version. + Downgrades Beacon database schemas. + + If a specific version number is provided, Beacon will only downgrade to that version (inclusive). + Otherwise, it will completely uninstall Beacon from your app's database. + + ## Example + + Run all migrations from current version down to the first: + + Beacon.Migration.down() + + Run migrations down to and including a specified version: + + Beacon.Migration.down(version: 2) + """ - def down do - @latest.down() + def down(opts \\ []) do + versions_to_run = + case opts[:version] do + nil -> @current_version..@initial_version + version -> @current_version..version + end + + Enum.each(versions_to_run, fn version -> + padded = String.pad_leading("#{version}", 3, "0") + + [Beacon.Migrations, "v#{padded}"] + |> Module.concat() + |> apply(:down, []) + end) end end diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex new file mode 100644 index 00000000..3a8a3f3b --- /dev/null +++ b/lib/beacon/migrations/v002.ex @@ -0,0 +1,41 @@ +defmodule Beacon.Migrations.V002 do + @moduledoc false + use Ecto.Migration + + def up do + create_if_not_exists table(:beacon_event_handlers, primary_key: false) do + add :id, :binary_id, primary_key: true + add :name, :text, null: false + add :code, :text, null: false + add :site, :text, null: false + + timestamps(type: :utc_datetime_usec) + end + + execute(""" + INSERT INTO beacon_event_handlers (id, name, code, site) + SELECT peh.id, peh.name, peh.code, p.site + FROM beacon_page_event_handlers as peh + JOIN beacon_pages as p + ON peh.page_id = p.id; + """) + + drop_if_exists table(:beacon_page_event_handlers) + end + + def down do + create_if_not_exists table(:beacon_page_event_handlers, primary_key: false) do + add :id, :binary_id, primary_key: true + add :name, :text, null: false + add :code, :text, null: false + + add :page_id, references(:beacon_pages, on_delete: :delete_all, type: :binary_id), null: false + + timestamps(type: :utc_datetime_usec) + end + + # global event handlers can't be converted back into page event handlers + + drop_if_exists table(:beacon_event_handlers) + end +end diff --git a/lib/errors.ex b/lib/errors.ex index 5ba00cde..7e666bce 100644 --- a/lib/errors.ex +++ b/lib/errors.ex @@ -83,7 +83,7 @@ end defmodule Beacon.Web.ServerError do @moduledoc """ - Raised when a `Beacon.Content.PageEventHandler` returns an invalid response. + Raised when a `Beacon.Content.EventHandler` returns an invalid response. If you're seeing this error, check the code in your site's event handlers, and ensure that each one returns `{:noreply, socket}`. diff --git a/mix.exs b/mix.exs index 2234714f..13a64f8a 100644 --- a/mix.exs +++ b/mix.exs @@ -47,8 +47,7 @@ defmodule Beacon.MixProject do GitHub: @source_url, Website: @homepage_url }, - files: - ~w(lib priv .formatter.exs mix.exs CHANGELOG.md LICENSE.md) + files: ~w(lib priv .formatter.exs mix.exs CHANGELOG.md LICENSE.md) ] end @@ -132,7 +131,7 @@ defmodule Beacon.MixProject do "Functions: Stylesheets": &(&1[:type] == :stylesheets), "Functions: Components": &(&1[:type] == :components), "Functions: Snippets": &(&1[:type] == :snippets), - "Functions: Page Event Handlers": &(&1[:type] == :page_event_handlers), + "Functions: Event Handlers": &(&1[:type] == :event_handlers), "Functions: Error Pages": &(&1[:type] == :error_pages), "Functions: Live Data": &(&1[:type] == :live_data) ], @@ -167,6 +166,7 @@ defmodule Beacon.MixProject do Beacon.Content.ComponentSlot, Beacon.Content.ComponentSlotAttr, Beacon.Content.ErrorPage, + Beacon.Content.EventHandler, Beacon.Content.Layout, Beacon.Content.LayoutEvent, Beacon.Content.LayoutSnapshot, @@ -176,7 +176,6 @@ defmodule Beacon.MixProject do Beacon.Content.Page.Event, Beacon.Content.Page.Helper, Beacon.Content.PageEvent, - Beacon.Content.PageEventHandler, Beacon.Content.PageSnapshot, Beacon.Content.PageVariant, Beacon.Content.Stylesheet, diff --git a/test/beacon/content_test.exs b/test/beacon/content_test.exs index 49b1456a..c6cd96e0 100644 --- a/test/beacon/content_test.exs +++ b/test/beacon/content_test.exs @@ -6,13 +6,13 @@ defmodule Beacon.ContentTest do alias Beacon.Content alias Beacon.Content.Component alias Beacon.Content.ErrorPage + alias Beacon.Content.EventHandler alias Beacon.Content.Layout alias Beacon.Content.LayoutEvent alias Beacon.Content.LayoutSnapshot alias Beacon.Content.LiveData alias Beacon.Content.Page alias Beacon.Content.PageEvent - alias Beacon.Content.PageEventHandler alias Beacon.Content.PageSnapshot alias Beacon.Content.PageVariant alias Beacon.BeaconTest.Repo @@ -602,7 +602,7 @@ defmodule Beacon.ContentTest do attrs = %{name: "Foo", code: "{:noreply, socket}"} assert {:ok, %Page{event_handlers: [event_handler]}} = Content.create_event_handler_for_page(page, attrs) - assert %PageEventHandler{name: "Foo", code: "{:noreply, socket}"} = event_handler + assert %EventHandler{name: "Foo", code: "{:noreply, socket}"} = event_handler end test "create triggers after_update_page lifecycle" do @@ -639,16 +639,16 @@ defmodule Beacon.ContentTest do test "update event handler OK" do page = page_fixture(%{format: :heex}) - event_handler = page_event_handler_fixture(%{page: page}) + event_handler = event_handler_fixture(%{page: page}) attrs = %{name: "Changed Name", code: "{:noreply, assign(socket, foo: :bar)}"} assert {:ok, %Page{event_handlers: [updated_event_handler]}} = Content.update_event_handler_for_page(page, event_handler, attrs) - assert %PageEventHandler{name: "Changed Name", code: "{:noreply, assign(socket, foo: :bar)}"} = updated_event_handler + assert %EventHandler{name: "Changed Name", code: "{:noreply, assign(socket, foo: :bar)}"} = updated_event_handler end test "update triggers after_update_page lifecycle" do page = page_fixture(site: :lifecycle_test) - event_handler = page_event_handler_fixture(%{page: page}) + event_handler = event_handler_fixture(%{page: page}) {:ok, %Page{}} = Content.update_event_handler_for_page(page, event_handler, %{name: "Changed"}) @@ -657,15 +657,15 @@ defmodule Beacon.ContentTest do test "update validates elixir code" do page = page_fixture(%{format: :heex}) - page_event_handler = page_event_handler_fixture(%{page: page}) + event_handler = event_handler_fixture(%{page: page}) attrs = %{code: "[1)"} - assert {:error, %{errors: [error]}} = Content.update_event_handler_for_page(page, page_event_handler, attrs) + assert {:error, %{errors: [error]}} = Content.update_event_handler_for_page(page, event_handler, attrs) {:code, {_, [compilation_error: compilation_error]}} = error assert compilation_error =~ "unexpected token: )" attrs = %{code: "if true, do false"} - assert {:error, %{errors: [error]}} = Content.update_event_handler_for_page(page, page_event_handler, attrs) + assert {:error, %{errors: [error]}} = Content.update_event_handler_for_page(page, event_handler, attrs) {:code, {_, [compilation_error: compilation_error]}} = error assert compilation_error =~ "unexpected reserved word: do" @@ -676,13 +676,13 @@ defmodule Beacon.ContentTest do | attrs = %{code: code} - assert {:ok, _} = Content.update_event_handler_for_page(page, page_event_handler, attrs) + assert {:ok, _} = Content.update_event_handler_for_page(page, event_handler, attrs) end test "delete event handler OK" do page = page_fixture(%{format: :heex}) - event_handler_1 = page_event_handler_fixture(%{page: page}) - event_handler_2 = page_event_handler_fixture(%{page: page}) + event_handler_1 = event_handler_fixture(%{page: page}) + event_handler_2 = event_handler_fixture(%{page: page}) assert {:ok, %Page{event_handlers: [^event_handler_2]}} = Content.delete_event_handler_from_page(page, event_handler_1) assert {:ok, %Page{event_handlers: []}} = Content.delete_event_handler_from_page(page, event_handler_2) @@ -690,7 +690,7 @@ defmodule Beacon.ContentTest do test "delete triggers after_update_page lifecycle" do page = page_fixture(site: :lifecycle_test) - event_handler = page_event_handler_fixture(%{page: page}) + event_handler = event_handler_fixture(%{page: page}) {:ok, %Page{}} = Content.delete_event_handler_from_page(page, event_handler) diff --git a/test/beacon_web/live/page_live_test.exs b/test/beacon_web/live/page_live_test.exs index 4ae7bd59..539c8432 100644 --- a/test/beacon_web/live/page_live_test.exs +++ b/test/beacon_web/live/page_live_test.exs @@ -82,7 +82,7 @@ defmodule Beacon.Web.Live.PageLiveTest do ) _page_home_form_submit_handler = - page_event_handler_fixture(%{ + event_handler_fixture(%{ page: page_home, name: "hello", code: """ diff --git a/test/support/fixtures.ex b/test/support/fixtures.ex index 06926146..d904a079 100644 --- a/test/support/fixtures.ex +++ b/test/support/fixtures.ex @@ -3,7 +3,7 @@ defmodule Beacon.Fixtures do alias Beacon.Content alias Beacon.Content.ErrorPage - alias Beacon.Content.PageEventHandler + alias Beacon.Content.EventHandler alias Beacon.Content.PageVariant alias Beacon.MediaLibrary alias Beacon.MediaLibrary.UploadMetadata @@ -171,18 +171,18 @@ defmodule Beacon.Fixtures do defp template_for(%{format: :heex} = _page), do: "
My Site
" defp template_for(%{format: :markdown} = _page), do: "# My site" - def page_event_handler_fixture(attrs \\ %{}) + def event_handler_fixture(attrs \\ %{}) - def page_event_handler_fixture(%{page: %Content.Page{} = page} = attrs), - do: page_event_handler_fixture(page, attrs) + def event_handler_fixture(%{page: %Content.Page{} = page} = attrs), + do: event_handler_fixture(page, attrs) - def page_event_handler_fixture(%{site: site, page_id: page_id} = attrs) do + def event_handler_fixture(%{site: site, page_id: page_id} = attrs) do site |> Content.get_page!(page_id) - |> page_event_handler_fixture(attrs) + |> event_handler_fixture(attrs) end - defp page_event_handler_fixture(page, attrs) do + defp event_handler_fixture(page, attrs) do full_attrs = %{ name: attrs[:name] || "Event Handler #{System.unique_integer([:positive])}", code: attrs[:code] || "{:noreply, socket}" @@ -190,7 +190,7 @@ defmodule Beacon.Fixtures do page |> Ecto.build_assoc(:event_handlers) - |> PageEventHandler.changeset(full_attrs) + |> EventHandler.changeset(full_attrs) |> repo(page).insert!() end From 5f3352c834e8426be85674f4124fcd7de03b9014 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:22:57 -0500 Subject: [PATCH 02/23] some fixes --- lib/beacon/content/event_handler.ex | 1 - lib/beacon/content/page.ex | 1 - lib/beacon/migration.ex | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/beacon/content/event_handler.ex b/lib/beacon/content/event_handler.ex index fb4b1be6..67990c7e 100644 --- a/lib/beacon/content/event_handler.ex +++ b/lib/beacon/content/event_handler.ex @@ -17,7 +17,6 @@ defmodule Beacon.Content.EventHandler do import Ecto.Changeset - alias Beacon.Content.Page alias Beacon.Types.Site alias Ecto.UUID diff --git a/lib/beacon/content/page.ex b/lib/beacon/content/page.ex index a5339382..ba199884 100644 --- a/lib/beacon/content/page.ex +++ b/lib/beacon/content/page.ex @@ -45,7 +45,6 @@ defmodule Beacon.Content.Page do belongs_to :layout, Content.Layout has_many :variants, Content.PageVariant - has_many :event_handlers, Content.EventHandler embeds_many :helpers, Helper diff --git a/lib/beacon/migration.ex b/lib/beacon/migration.ex index 2b73546f..84174069 100644 --- a/lib/beacon/migration.ex +++ b/lib/beacon/migration.ex @@ -81,7 +81,7 @@ defmodule Beacon.Migration do Enum.each(versions_to_run, fn version -> padded = String.pad_leading("#{version}", 3, "0") - [Beacon.Migrations, "v#{padded}"] + [Beacon.Migrations, "V#{padded}"] |> Module.concat() |> apply(:up, []) end) @@ -114,7 +114,7 @@ defmodule Beacon.Migration do Enum.each(versions_to_run, fn version -> padded = String.pad_leading("#{version}", 3, "0") - [Beacon.Migrations, "v#{padded}"] + [Beacon.Migrations, "V#{padded}"] |> Module.concat() |> apply(:down, []) end) From 8dfc35ac841c399df534a5b2e9b33e6a89c1011e Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:39:55 -0500 Subject: [PATCH 03/23] remove preload --- lib/beacon/content.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/beacon/content.ex b/lib/beacon/content.ex index 2d3eed70..cb4e7afa 100644 --- a/lib/beacon/content.ex +++ b/lib/beacon/content.ex @@ -702,7 +702,7 @@ defmodule Beacon.Content do @doc false def create_page_snapshot(page, event) do - page = repo(page).preload(page, [:variants, :event_handlers]) + page = repo(page).preload(page, :variants) attrs = %{ "site" => page.site, From 2e36c28479936bbe588dacff8093acf4ea8c85a3 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:48:45 -0500 Subject: [PATCH 04/23] update loader --- lib/beacon/content.ex | 8 ++++++++ lib/beacon/loader/page.ex | 11 ++++++----- lib/beacon/migration.ex | 8 ++++---- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/beacon/content.ex b/lib/beacon/content.ex index cb4e7afa..3ccaa4aa 100644 --- a/lib/beacon/content.ex +++ b/lib/beacon/content.ex @@ -3531,6 +3531,14 @@ defmodule Beacon.Content do EventHandler.changeset(event_handler, attrs) end + @doc """ + Lists all event handlers for a given Beacon site. + """ + @spec list_event_handlers_for_site(Site.t()) :: [EventHandler.t()] + def list_event_handlers_for_site(site) do + Repo.all(from eh in EventHandler, where: [site: ^site]) + end + @doc """ Creates a new event handler. """ diff --git a/lib/beacon/loader/page.ex b/lib/beacon/loader/page.ex index e8262cdf..e97a180d 100644 --- a/lib/beacon/loader/page.ex +++ b/lib/beacon/loader/page.ex @@ -1,11 +1,12 @@ defmodule Beacon.Loader.Page do @moduledoc false - - require Logger + alias Beacon.Content alias Beacon.Lifecycle alias Beacon.Loader alias Beacon.Template.HEEx + require Logger + def module_name(site, page_id), do: Loader.module_name(site, "Page#{page_id}") def build_ast(site, page) do @@ -96,7 +97,7 @@ defmodule Beacon.Loader.Page do defp interpolate_raw_schema_record(schema, page) when is_map(schema) do render = fn key, value, page -> - case Beacon.Content.render_snippet(value, %{page: page, live_data: %{}}) do + case Content.render_snippet(value, %{page: page, live_data: %{}}) do {:ok, new_value} -> {key, new_value} @@ -124,10 +125,10 @@ defmodule Beacon.Loader.Page do end defp handle_event(page) do - %{site: site, event_handlers: event_handlers} = page + event_handlers = Content.list_event_handlers_for_site(page.site) Enum.map(event_handlers, fn event_handler -> - Beacon.safe_code_check!(site, event_handler.code) + Beacon.safe_code_check!(page.site, event_handler.code) quote do def handle_event(unquote(event_handler.name), var!(event_params), var!(socket)) do diff --git a/lib/beacon/migration.ex b/lib/beacon/migration.ex index 84174069..6c8f211f 100644 --- a/lib/beacon/migration.ex +++ b/lib/beacon/migration.ex @@ -74,8 +74,8 @@ defmodule Beacon.Migration do def up(opts \\ []) do versions_to_run = case opts[:version] do - nil -> @initial_version..@current_version - version -> @initial_version..version + nil -> @initial_version..@current_version//1 + version -> @initial_version..version//1 end Enum.each(versions_to_run, fn version -> @@ -107,8 +107,8 @@ defmodule Beacon.Migration do def down(opts \\ []) do versions_to_run = case opts[:version] do - nil -> @current_version..@initial_version - version -> @current_version..version + nil -> @current_version..@initial_version//-1 + version -> @current_version..version//-1 end Enum.each(versions_to_run, fn version -> From d95dee094d6ada5e9efc2cb77e0fda0fc3ce7070 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:50:26 -0500 Subject: [PATCH 05/23] fix repo error --- lib/beacon/content.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/beacon/content.ex b/lib/beacon/content.ex index 3ccaa4aa..4821e420 100644 --- a/lib/beacon/content.ex +++ b/lib/beacon/content.ex @@ -3536,7 +3536,7 @@ defmodule Beacon.Content do """ @spec list_event_handlers_for_site(Site.t()) :: [EventHandler.t()] def list_event_handlers_for_site(site) do - Repo.all(from eh in EventHandler, where: [site: ^site]) + repo(site).all(from eh in EventHandler, where: [site: ^site]) end @doc """ From 407dabcda1c68285abc904425164a15d8263ec3a Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 2 Aug 2024 15:57:44 -0500 Subject: [PATCH 06/23] test fixes --- lib/beacon/migration.ex | 12 +++---- test/beacon/content_test.exs | 67 +++++++++--------------------------- 2 files changed, 20 insertions(+), 59 deletions(-) diff --git a/lib/beacon/migration.ex b/lib/beacon/migration.ex index 6c8f211f..f2042dc1 100644 --- a/lib/beacon/migration.ex +++ b/lib/beacon/migration.ex @@ -80,10 +80,8 @@ defmodule Beacon.Migration do Enum.each(versions_to_run, fn version -> padded = String.pad_leading("#{version}", 3, "0") - - [Beacon.Migrations, "V#{padded}"] - |> Module.concat() - |> apply(:up, []) + module = Module.concat([Beacon.Migrations, "V#{padded}"]) + module.up() end) end @@ -113,10 +111,8 @@ defmodule Beacon.Migration do Enum.each(versions_to_run, fn version -> padded = String.pad_leading("#{version}", 3, "0") - - [Beacon.Migrations, "V#{padded}"] - |> Module.concat() - |> apply(:down, []) + module = Module.concat([Beacon.Migrations, "V#{padded}"]) + module.down() end) end end diff --git a/test/beacon/content_test.exs b/test/beacon/content_test.exs index c6cd96e0..dcadd091 100644 --- a/test/beacon/content_test.exs +++ b/test/beacon/content_test.exs @@ -598,32 +598,20 @@ defmodule Beacon.ContentTest do describe "event_handlers" do test "create event handler OK" do - page = page_fixture() - attrs = %{name: "Foo", code: "{:noreply, socket}"} + attrs = %{name: "Foo", code: "{:noreply, socket}", site: :my_site} - assert {:ok, %Page{event_handlers: [event_handler]}} = Content.create_event_handler_for_page(page, attrs) + assert {:ok, event_handler} = Content.create_event_handler(attrs) assert %EventHandler{name: "Foo", code: "{:noreply, socket}"} = event_handler end - test "create triggers after_update_page lifecycle" do - page = page_fixture(site: :lifecycle_test) - attrs = %{name: "Foo", code: "{:noreply, socket}"} - - {:ok, %Page{}} = Content.create_event_handler_for_page(page, attrs) - - assert_receive :lifecycle_after_update_page - end - test "create validates elixir code" do - page = page_fixture(%{format: :heex}) - - attrs = %{name: "test", code: "[1)"} - assert {:error, %{errors: [error]}} = Content.create_event_handler_for_page(page, attrs) + attrs = %{name: "test", code: "[1)", site: :my_site} + assert {:error, %{errors: [error]}} = Content.create_event_handler(attrs) {:code, {_, [compilation_error: compilation_error]}} = error assert compilation_error =~ "unexpected token: )" - attrs = %{name: "test", code: "if true, do false"} - assert {:error, %{errors: [error]}} = Content.create_event_handler_for_page(page, attrs) + attrs = %{name: "test", code: "if true, do false", site: :my_site} + assert {:error, %{errors: [error]}} = Content.create_event_handler(attrs) {:code, {_, [compilation_error: compilation_error]}} = error assert compilation_error =~ "unexpected reserved word: do" @@ -633,39 +621,28 @@ defmodule Beacon.ContentTest do {:noreply, assign(socket, res: res)} | - attrs = %{name: "test", code: code} - assert {:ok, _} = Content.create_event_handler_for_page(page, attrs) + attrs = %{name: "test", code: code, site: :my_site} + assert {:ok, _} = Content.create_event_handler(attrs) end test "update event handler OK" do - page = page_fixture(%{format: :heex}) - event_handler = event_handler_fixture(%{page: page}) + event_handler = event_handler_fixture() attrs = %{name: "Changed Name", code: "{:noreply, assign(socket, foo: :bar)}"} - assert {:ok, %Page{event_handlers: [updated_event_handler]}} = Content.update_event_handler_for_page(page, event_handler, attrs) + assert {:ok, updated_event_handler} = Content.update_event_handler(event_handler, attrs) assert %EventHandler{name: "Changed Name", code: "{:noreply, assign(socket, foo: :bar)}"} = updated_event_handler end - test "update triggers after_update_page lifecycle" do - page = page_fixture(site: :lifecycle_test) - event_handler = event_handler_fixture(%{page: page}) - - {:ok, %Page{}} = Content.update_event_handler_for_page(page, event_handler, %{name: "Changed"}) - - assert_receive :lifecycle_after_update_page - end - test "update validates elixir code" do - page = page_fixture(%{format: :heex}) - event_handler = event_handler_fixture(%{page: page}) + event_handler = event_handler_fixture() attrs = %{code: "[1)"} - assert {:error, %{errors: [error]}} = Content.update_event_handler_for_page(page, event_handler, attrs) + assert {:error, %{errors: [error]}} = Content.update_event_handler(event_handler, attrs) {:code, {_, [compilation_error: compilation_error]}} = error assert compilation_error =~ "unexpected token: )" attrs = %{code: "if true, do false"} - assert {:error, %{errors: [error]}} = Content.update_event_handler_for_page(page, event_handler, attrs) + assert {:error, %{errors: [error]}} = Content.update_event_handler(event_handler, attrs) {:code, {_, [compilation_error: compilation_error]}} = error assert compilation_error =~ "unexpected reserved word: do" @@ -676,25 +653,13 @@ defmodule Beacon.ContentTest do | attrs = %{code: code} - assert {:ok, _} = Content.update_event_handler_for_page(page, event_handler, attrs) + assert {:ok, _} = Content.update_event_handler(event_handler, attrs) end test "delete event handler OK" do - page = page_fixture(%{format: :heex}) - event_handler_1 = event_handler_fixture(%{page: page}) - event_handler_2 = event_handler_fixture(%{page: page}) + event_handler = event_handler_fixture() - assert {:ok, %Page{event_handlers: [^event_handler_2]}} = Content.delete_event_handler_from_page(page, event_handler_1) - assert {:ok, %Page{event_handlers: []}} = Content.delete_event_handler_from_page(page, event_handler_2) - end - - test "delete triggers after_update_page lifecycle" do - page = page_fixture(site: :lifecycle_test) - event_handler = event_handler_fixture(%{page: page}) - - {:ok, %Page{}} = Content.delete_event_handler_from_page(page, event_handler) - - assert_receive :lifecycle_after_update_page + assert {:ok, ^event_handler} = Content.delete_event_handler(event_handler) end end From 90c4361664e4b8cc0e4b3225964f4006c10ad899 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:04:29 -0500 Subject: [PATCH 07/23] fixes --- test/beacon/lifecycle/template_test.exs | 2 +- test/beacon_web/live/page_live_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/beacon/lifecycle/template_test.exs b/test/beacon/lifecycle/template_test.exs index b41fd0bd..ad799fba 100644 --- a/test/beacon/lifecycle/template_test.exs +++ b/test/beacon/lifecycle/template_test.exs @@ -16,7 +16,7 @@ defmodule Beacon.Lifecycle.TemplateTest do end test "render_template" do - page = published_page_fixture(site: "my_site") |> Repo.preload([:event_handlers, :variants]) + page = published_page_fixture(site: "my_site") |> Repo.preload(:variants) Beacon.Loader.reload_page_module(page.site, page.id) env = Beacon.Web.PageLive.make_env(:my_site) diff --git a/test/beacon_web/live/page_live_test.exs b/test/beacon_web/live/page_live_test.exs index 539c8432..41824b08 100644 --- a/test/beacon_web/live/page_live_test.exs +++ b/test/beacon_web/live/page_live_test.exs @@ -83,7 +83,7 @@ defmodule Beacon.Web.Live.PageLiveTest do _page_home_form_submit_handler = event_handler_fixture(%{ - page: page_home, + site: :my_site, name: "hello", code: """ {:noreply, assign(socket, :message, "Hello \#{event_params["greeting"]["name"]}!")} From e822efc41a6f82cf1c4ed9fd53641581f7cd764b Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:12:24 -0500 Subject: [PATCH 08/23] fixes --- test/beacon/loader/page_test.exs | 2 +- test/support/fixtures.ex | 21 +++++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/test/beacon/loader/page_test.exs b/test/beacon/loader/page_test.exs index 6bd0ce27..bb9231ce 100644 --- a/test/beacon/loader/page_test.exs +++ b/test/beacon/loader/page_test.exs @@ -69,7 +69,7 @@ defmodule Beacon.Loader.PageTest do describe "render" do test "render primary template" do - page = published_page_fixture(site: "my_site", path: "/1") |> Repo.preload([:event_handlers, :variants]) + page = published_page_fixture(site: "my_site", path: "/1") |> Repo.preload(:variants) {:ok, module} = Loader.reload_page_module(page.site, page.id) assert %Phoenix.LiveView.Rendered{static: ["
\n

my_site#home

\n
"]} = module.render(%{}) end diff --git a/test/support/fixtures.ex b/test/support/fixtures.ex index d904a079..b52f7aa5 100644 --- a/test/support/fixtures.ex +++ b/test/support/fixtures.ex @@ -171,27 +171,16 @@ defmodule Beacon.Fixtures do defp template_for(%{format: :heex} = _page), do: "
My Site
" defp template_for(%{format: :markdown} = _page), do: "# My site" - def event_handler_fixture(attrs \\ %{}) - - def event_handler_fixture(%{page: %Content.Page{} = page} = attrs), - do: event_handler_fixture(page, attrs) - - def event_handler_fixture(%{site: site, page_id: page_id} = attrs) do - site - |> Content.get_page!(page_id) - |> event_handler_fixture(attrs) - end - - defp event_handler_fixture(page, attrs) do + def event_handler_fixture(attrs \\ %{}) do full_attrs = %{ name: attrs[:name] || "Event Handler #{System.unique_integer([:positive])}", - code: attrs[:code] || "{:noreply, socket}" + code: attrs[:code] || "{:noreply, socket}", + site: attrs[:site] || :my_site } - page - |> Ecto.build_assoc(:event_handlers) + %EventHandler{} |> EventHandler.changeset(full_attrs) - |> repo(page).insert!() + |> repo(full_attrs.site).insert!() end def error_page_fixture(attrs \\ %{}) do From 48b8b929ccf408954424678262e8c428aca64d7e Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:43:57 -0500 Subject: [PATCH 09/23] fix --- test/beacon/content_test.exs | 4 ++-- test/beacon_web/live/page_live_test.exs | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/beacon/content_test.exs b/test/beacon/content_test.exs index dcadd091..7f48131e 100644 --- a/test/beacon/content_test.exs +++ b/test/beacon/content_test.exs @@ -657,9 +657,9 @@ defmodule Beacon.ContentTest do end test "delete event handler OK" do - event_handler = event_handler_fixture() + %{id: id} = event_handler = event_handler_fixture() - assert {:ok, ^event_handler} = Content.delete_event_handler(event_handler) + assert {:ok, %{id: ^id}} = Content.delete_event_handler(event_handler) end end diff --git a/test/beacon_web/live/page_live_test.exs b/test/beacon_web/live/page_live_test.exs index 41824b08..408221cf 100644 --- a/test/beacon_web/live/page_live_test.exs +++ b/test/beacon_web/live/page_live_test.exs @@ -181,7 +181,7 @@ defmodule Beacon.Web.Live.PageLiveTest do layout = published_layout_fixture() page = - [ + published_page_fixture( site: "my_site", layout_id: layout.id, path: "/page/meta-tag", @@ -192,9 +192,7 @@ defmodule Beacon.Web.Live.PageLiveTest do %{"property" => "og:url", "content" => "http://example.com{{ page.path }}"}, %{"property" => "og:image", "content" => "{{ live_data.image }}"} ] - ] - |> published_page_fixture() - |> Beacon.BeaconTest.Repo.preload(:event_handlers) + ) live_data = live_data_fixture(path: "/page/meta-tag") live_data_assign_fixture(live_data: live_data, format: :text, key: "image", value: "http://img.example.com") From cb72042671a41eff9ff1cb094783a08660badf88 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Mon, 12 Aug 2024 08:56:47 -0500 Subject: [PATCH 10/23] group query --- lib/beacon/content.ex | 4 ++-- lib/beacon/loader/page.ex | 2 +- lib/beacon/migrations/v002.ex | 3 ++- test/beacon/content_test.exs | 8 ++++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/beacon/content.ex b/lib/beacon/content.ex index 4821e420..4d6528af 100644 --- a/lib/beacon/content.ex +++ b/lib/beacon/content.ex @@ -3534,8 +3534,8 @@ defmodule Beacon.Content do @doc """ Lists all event handlers for a given Beacon site. """ - @spec list_event_handlers_for_site(Site.t()) :: [EventHandler.t()] - def list_event_handlers_for_site(site) do + @spec list_event_handlers(Site.t()) :: [EventHandler.t()] + def list_event_handlers(site) do repo(site).all(from eh in EventHandler, where: [site: ^site]) end diff --git a/lib/beacon/loader/page.ex b/lib/beacon/loader/page.ex index e97a180d..9d5a934e 100644 --- a/lib/beacon/loader/page.ex +++ b/lib/beacon/loader/page.ex @@ -125,7 +125,7 @@ defmodule Beacon.Loader.Page do end defp handle_event(page) do - event_handlers = Content.list_event_handlers_for_site(page.site) + event_handlers = Content.list_event_handlers(page.site) Enum.map(event_handlers, fn event_handler -> Beacon.safe_code_check!(page.site, event_handler.code) diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex index 3a8a3f3b..157a9d66 100644 --- a/lib/beacon/migrations/v002.ex +++ b/lib/beacon/migrations/v002.ex @@ -17,7 +17,8 @@ defmodule Beacon.Migrations.V002 do SELECT peh.id, peh.name, peh.code, p.site FROM beacon_page_event_handlers as peh JOIN beacon_pages as p - ON peh.page_id = p.id; + ON peh.page_id = p.id + GROUP BY peh.name, p.site; """) drop_if_exists table(:beacon_page_event_handlers) diff --git a/test/beacon/content_test.exs b/test/beacon/content_test.exs index 7f48131e..c8a2db1e 100644 --- a/test/beacon/content_test.exs +++ b/test/beacon/content_test.exs @@ -597,6 +597,14 @@ defmodule Beacon.ContentTest do end describe "event_handlers" do + test "list_event_handlers/1" do + event_handlers = for _ <- 1..3, do: event_handler_fixture(site: :my_site) + + result = Content.list_event_handlers(:my_site) + + assert Enum.sort(event_handlers) == Enum.sort(result) + end + test "create event handler OK" do attrs = %{name: "Foo", code: "{:noreply, socket}", site: :my_site} From 3aee0762ade86d5ccdbcc8d6ba96795b0de3b534 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:28:12 -0500 Subject: [PATCH 11/23] try with max() --- lib/beacon/migrations/v002.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex index 157a9d66..9ee92664 100644 --- a/lib/beacon/migrations/v002.ex +++ b/lib/beacon/migrations/v002.ex @@ -14,7 +14,7 @@ defmodule Beacon.Migrations.V002 do execute(""" INSERT INTO beacon_event_handlers (id, name, code, site) - SELECT peh.id, peh.name, peh.code, p.site + SELECT max(peh.id), peh.name, max(peh.code), p.site FROM beacon_page_event_handlers as peh JOIN beacon_pages as p ON peh.page_id = p.id From 2356de66e835caa02918da9a55c61b9049c217aa Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:32:29 -0500 Subject: [PATCH 12/23] cast id as text --- lib/beacon/migrations/v002.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex index 9ee92664..f3d61e25 100644 --- a/lib/beacon/migrations/v002.ex +++ b/lib/beacon/migrations/v002.ex @@ -14,7 +14,7 @@ defmodule Beacon.Migrations.V002 do execute(""" INSERT INTO beacon_event_handlers (id, name, code, site) - SELECT max(peh.id), peh.name, max(peh.code), p.site + SELECT max(peh.id::text), peh.name, max(peh.code), p.site FROM beacon_page_event_handlers as peh JOIN beacon_pages as p ON peh.page_id = p.id From 44493b565cc0ae1fa37dfa66f9a1a362d005636a Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:39:05 -0500 Subject: [PATCH 13/23] cast the cast --- lib/beacon/migrations/v002.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex index f3d61e25..2a84cfee 100644 --- a/lib/beacon/migrations/v002.ex +++ b/lib/beacon/migrations/v002.ex @@ -14,7 +14,7 @@ defmodule Beacon.Migrations.V002 do execute(""" INSERT INTO beacon_event_handlers (id, name, code, site) - SELECT max(peh.id::text), peh.name, max(peh.code), p.site + SELECT max(peh.id::text)::uuid, peh.name, max(peh.code), p.site FROM beacon_page_event_handlers as peh JOIN beacon_pages as p ON peh.page_id = p.id From b5bf1c52351063c320282835d94de5360cfb4958 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:26:31 -0500 Subject: [PATCH 14/23] convert to elixir --- lib/beacon/migrations/v002.ex | 41 ++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex index 2a84cfee..45f7b9c2 100644 --- a/lib/beacon/migrations/v002.ex +++ b/lib/beacon/migrations/v002.ex @@ -2,6 +2,8 @@ defmodule Beacon.Migrations.V002 do @moduledoc false use Ecto.Migration + import Ecto.Query + def up do create_if_not_exists table(:beacon_event_handlers, primary_key: false) do add :id, :binary_id, primary_key: true @@ -12,14 +14,37 @@ defmodule Beacon.Migrations.V002 do timestamps(type: :utc_datetime_usec) end - execute(""" - INSERT INTO beacon_event_handlers (id, name, code, site) - SELECT max(peh.id::text)::uuid, peh.name, max(peh.code), p.site - FROM beacon_page_event_handlers as peh - JOIN beacon_pages as p - ON peh.page_id = p.id - GROUP BY peh.name, p.site; - """) + create_if_not_exists table("beacon_page_event_handlers", primary_key: false) do + add :id, :binary_id, primary_key: true + add :name, :text, null: false + add :code, :text, null: false + add :site, :text, null: false + + timestamps(type: :utc_datetime_usec) + end + + repo().all( + from(peh in "beacon_page_event_handlers", + join: p in "beacon_pages", + on: peh.page_id == p.id, + group_by: [peh.page_id, p.site], + select: {max(peh.id), peh.name, max(peh.code), p.site} + ) + ) + |> Enum.map(fn {id, name, code, site} -> + now = DateTime.utc_now() + %{id: id, name: name, code: code, site: site, inserted_at: now, updated_at: now} + end) + |> then(&repo().insert_all("beacon_event_handlers", &1, [])) + + # execute(""" + # INSERT INTO beacon_event_handlers (id, name, code, site) + # SELECT max(peh.id::text)::uuid, peh.name, max(peh.code), p.site + # FROM beacon_page_event_handlers as peh + # JOIN beacon_pages as p + # ON peh.page_id = p.id + # GROUP BY peh.name, p.site; + # """) drop_if_exists table(:beacon_page_event_handlers) end From 4e77ee2569b73b93be708a9392d9ecb2fd0d1386 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:52:53 -0500 Subject: [PATCH 15/23] fix migration --- lib/beacon/migrations/v002.ex | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex index 45f7b9c2..fd59b88a 100644 --- a/lib/beacon/migrations/v002.ex +++ b/lib/beacon/migrations/v002.ex @@ -14,38 +14,25 @@ defmodule Beacon.Migrations.V002 do timestamps(type: :utc_datetime_usec) end - create_if_not_exists table("beacon_page_event_handlers", primary_key: false) do - add :id, :binary_id, primary_key: true - add :name, :text, null: false - add :code, :text, null: false - add :site, :text, null: false - - timestamps(type: :utc_datetime_usec) - end + flush() repo().all( from(peh in "beacon_page_event_handlers", join: p in "beacon_pages", on: peh.page_id == p.id, - group_by: [peh.page_id, p.site], - select: {max(peh.id), peh.name, max(peh.code), p.site} + select: %{site: p.site, name: peh.name, code: peh.code}, + # distinct saves us memory in cases of duplicate code + distinct: true ) ) - |> Enum.map(fn {id, name, code, site} -> + # we still need to avoid duplicates where the code is different + |> Enum.group_by(&{&1.site, &1.name}, & &1.code) + |> Enum.map(fn {{site, name}, [code | _]} -> now = DateTime.utc_now() - %{id: id, name: name, code: code, site: site, inserted_at: now, updated_at: now} + %{id: Ecto.UUID.generate(), name: name, code: code, site: site, inserted_at: now, updated_at: now} end) |> then(&repo().insert_all("beacon_event_handlers", &1, [])) - # execute(""" - # INSERT INTO beacon_event_handlers (id, name, code, site) - # SELECT max(peh.id::text)::uuid, peh.name, max(peh.code), p.site - # FROM beacon_page_event_handlers as peh - # JOIN beacon_pages as p - # ON peh.page_id = p.id - # GROUP BY peh.name, p.site; - # """) - drop_if_exists table(:beacon_page_event_handlers) end From ec1ebb1b11cd94654236b60b618805445c71bbe7 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Thu, 22 Aug 2024 13:24:12 -0400 Subject: [PATCH 16/23] changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 995099a8..e35a0d08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.1.0-rc.1 + +### Enhancements + * Added Shared Event Handlers which are global event handlers shared among all pages. + That's a simple model to work with where a layout, component, or multiple pages may share the same event handler, + for example a newsletter subscription form in a component called in a layout doesn't need to duplicate the same + event handler in all pages. + +### Breaking Changes + * Remove Page Event Handlers in favor of Shared Event Handlers. + With Shared Event Handlers, it doesn't make sense to have page event hanlders unless overriding becomes a neccessity. + The data is automatically migrated in a best-effort way, duplicated event handler names (from multiple pages) are + consolidated into a single shared event handler. See the migration `V0002` for more info. + ## 0.1.0-rc.0 (2024-08-02) ### Enhancements From 9d6871d61f80dbb1e275225ad29bf0efc573341b Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:03:00 -0500 Subject: [PATCH 17/23] changelogs edits --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e35a0d08..38f83dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,9 @@ ### Breaking Changes * Remove Page Event Handlers in favor of Shared Event Handlers. - With Shared Event Handlers, it doesn't make sense to have page event hanlders unless overriding becomes a neccessity. + With Shared Event Handlers, it doesn't make sense to have page event handlers unless overriding becomes a neccessity. The data is automatically migrated in a best-effort way, duplicated event handler names (from multiple pages) are - consolidated into a single shared event handler. See the migration `V0002` for more info. + consolidated into a single shared event handler. See the migration `V002` for more info. ## 0.1.0-rc.0 (2024-08-02) From 40babaa39ba1618c6f59d42db6ea41c7575d6916 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:03:25 -0500 Subject: [PATCH 18/23] eventhandlers loader --- lib/beacon/boot.ex | 3 ++- lib/beacon/content.ex | 9 +++++++-- lib/beacon/loader.ex | 13 +++++++++++++ lib/beacon/loader/event_handlers.ex | 28 ++++++++++++++++++++++++++++ lib/beacon/loader/worker.ex | 8 ++++++++ lib/beacon/web/beacon_assigns.ex | 3 +++ lib/beacon/web/live/page_live.ex | 4 ++-- 7 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 lib/beacon/loader/event_handlers.ex diff --git a/lib/beacon/boot.ex b/lib/beacon/boot.ex index c85cb656..208fbc33 100644 --- a/lib/beacon/boot.ex +++ b/lib/beacon/boot.ex @@ -55,7 +55,8 @@ defmodule Beacon.Boot do Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_live_data_module(config.site) end), Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_layouts_modules(config.site) end), Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_error_page_module(config.site) end), - Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_pages_modules(config.site, per_page: 20) end) + Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_pages_modules(config.site, per_page: 20) end), + Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_event_handlers_module(config.site) end) # TODO: load main pages (order_by: path, per_page: 10) to avoid SEO issues ] diff --git a/lib/beacon/content.ex b/lib/beacon/content.ex index 4d6528af..dd315707 100644 --- a/lib/beacon/content.ex +++ b/lib/beacon/content.ex @@ -3553,7 +3553,9 @@ defmodule Beacon.Content do site = Changeset.get_field(changeset, :site) - repo(site).insert(changeset) + changeset + |> repo(site).insert() + |> tap(&maybe_broadcast_updated_content_event(&1, :event_handler)) end @doc """ @@ -3566,6 +3568,7 @@ defmodule Beacon.Content do |> EventHandler.changeset(attrs) |> validate_event_handler() |> repo(event_handler).update() + |> tap(&maybe_broadcast_updated_content_event(&1, :event_handler)) end defp validate_event_handler(changeset) do @@ -3582,7 +3585,9 @@ defmodule Beacon.Content do @doc type: :event_handlers @spec delete_event_handler(EventHandler.t()) :: {:ok, EventHandler.t()} | {:error, Changeset.t()} def delete_event_handler(event_handler) do - repo(event_handler).delete(event_handler) + event_handler + |> repo(event_handler).delete() + |> tap(&maybe_broadcast_updated_content_event(&1, :event_handler)) end # PAGE VARIANTS diff --git a/lib/beacon/loader.ex b/lib/beacon/loader.ex index 846668ef..dd019bed 100644 --- a/lib/beacon/loader.ex +++ b/lib/beacon/loader.ex @@ -141,6 +141,10 @@ defmodule Beacon.Loader do Loader.Stylesheet.module_name(site) end + def fetch_event_handlers_module(site) do + Loader.EventHandlers.module_name(site) + end + def fetch_layouts_modules(site) do Enum.map(Content.list_published_layouts(site), fn layout -> fetch_layout_module(layout.site, layout.id) @@ -189,6 +193,10 @@ defmodule Beacon.Loader do GenServer.call(worker(site), :reload_stylesheet_module, @timeout) end + def reload_event_handlers_module(site) do + GenServer.call(worker(site), :reload_event_handlers_module, @timeout) + end + def reload_layouts_modules(site) do Enum.map(Content.list_published_layouts(site), &reload_layout_module(&1.site, &1.id)) end @@ -288,6 +296,11 @@ defmodule Beacon.Loader do {:noreply, config} end + def handle_info({:content_updated, :event_handler, %{site: site}}, config) do + reload_event_handlers_module(site) + {:noreply, config} + end + def handle_info(msg, config) do raise inspect(msg) Logger.warning("Beacon.Loader can not handle the message: #{inspect(msg)}") diff --git a/lib/beacon/loader/event_handlers.ex b/lib/beacon/loader/event_handlers.ex new file mode 100644 index 00000000..21acb6ac --- /dev/null +++ b/lib/beacon/loader/event_handlers.ex @@ -0,0 +1,28 @@ +defmodule Beacon.Loader.EventHandlers do + @moduledoc false + alias Beacon.Loader + + def module_name(site), do: Loader.module_name(site, "EventHandlers") + + def build_ast(site, event_handlers) do + module = module_name(site) + functions = Enum.map(event_handlers, &build_fn/1) + + quote do + defmodule unquote(module) do + (unquote_splicing(functions)) + end + end + end + + defp build_fn(event_handler) do + %{site: site, name: name, code: code} = event_handler + Beacon.safe_code_check!(site, code) + + quote do + def handle_event(unquote(name), var!(event_params), var!(socket)) do + unquote(Code.string_to_quoted!(code)) + end + end + end +end diff --git a/lib/beacon/loader/worker.ex b/lib/beacon/loader/worker.ex index fcb67466..e1b21a13 100644 --- a/lib/beacon/loader/worker.ex +++ b/lib/beacon/loader/worker.ex @@ -381,6 +381,14 @@ defmodule Beacon.Loader.Worker do stop(result, config) end + def handle_call(:reload_event_handlers_module, _from, config) do + %{site: site} = config + event_handlers = Content.list_event_handlers(site) + ast = Loader.EventHandlers.build_ast(site, event_handlers) + result = compile_module(site, ast, "event_handlers") + stop(result, config) + end + def handle_call({:reload_layout_module, layout_id}, _from, config) do %{site: site} = config layout = Beacon.Content.get_published_layout(site, layout_id) diff --git a/lib/beacon/web/beacon_assigns.ex b/lib/beacon/web/beacon_assigns.ex index 11eb99c6..f757fb49 100644 --- a/lib/beacon/web/beacon_assigns.ex +++ b/lib/beacon/web/beacon_assigns.ex @@ -39,6 +39,7 @@ defmodule Beacon.Web.BeaconAssigns do private: %{ page_module: nil, components_module: nil, + event_handlers_module: nil, live_data_keys: [], live_path: [] } @@ -65,6 +66,7 @@ defmodule Beacon.Web.BeaconAssigns do path_params = Beacon.Router.path_params(page.path, path_info) page_title = Beacon.Web.DataSource.page_title(site, page.id, live_data) components_module = Beacon.Loader.Components.module_name(site) + event_handlers_module = Beacon.Loader.EventHandlers.module_name(site) %__MODULE__{ site: page.site, @@ -74,6 +76,7 @@ defmodule Beacon.Web.BeaconAssigns do private: %{ page_module: page_module, components_module: components_module, + event_handlers_module: event_handlers_module, live_data_keys: Map.keys(live_data), live_path: path_info } diff --git a/lib/beacon/web/live/page_live.ex b/lib/beacon/web/live/page_live.ex index 242dc242..c4b481de 100644 --- a/lib/beacon/web/live/page_live.ex +++ b/lib/beacon/web/live/page_live.ex @@ -64,12 +64,12 @@ defmodule Beacon.Web.PageLive do end def handle_event(event_name, event_params, socket) do - %{beacon: %{private: %{page_module: page_module, live_path: live_path}}} = socket.assigns + %{page_module: page_module, live_path: live_path, event_handlers_module: event_handlers_module} = socket.assigns.beacon.private %{site: site, id: page_id} = Beacon.apply_mfa(page_module, :page_assigns, [[:site, :id]]) result = Beacon.apply_mfa( - page_module, + event_handlers_module, :handle_event, [event_name, event_params, socket], context: %{site: site, page_id: page_id, live_path: live_path} From a991c52248416da8fb2eba1aeb76ebc9a9b5109e Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:36:59 -0500 Subject: [PATCH 19/23] setup test helper --- test/test_helper.exs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/test/test_helper.exs b/test/test_helper.exs index 95cc72b8..ceae09d9 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -159,28 +159,23 @@ Supervisor.start_link( ) # TODO: better control :booted default data when we introduce Beacon.Test functions -Beacon.BeaconTest.Repo.delete_all(Beacon.Content.Component) -Beacon.BeaconTest.Repo.delete_all(Beacon.Content.ErrorPage) -Beacon.BeaconTest.Repo.delete_all(Beacon.Content.Page) -Beacon.BeaconTest.Repo.delete_all(Beacon.Content.Layout) +Enum.each( + [ + Beacon.Content.Component, + Beacon.Content.ErrorPage, + Beacon.Content.Page, + Beacon.Content.Layout, + Beacon.Content.EventHandler + ], + &Beacon.BeaconTest.Repo.delete_all/1 +) # TODO: add hooks into Beacon.Testing to reload these shared/global modules -Beacon.Loader.reload_routes_module(:my_site) -Beacon.Loader.reload_routes_module(:not_booted) -Beacon.Loader.reload_routes_module(:booted) -Beacon.Loader.reload_routes_module(:s3_site) -Beacon.Loader.reload_routes_module(:data_source_test) -Beacon.Loader.reload_routes_module(:default_meta_tags_test) -Beacon.Loader.reload_routes_module(:lifecycle_test) -Beacon.Loader.reload_routes_module(:lifecycle_test_fail) -Beacon.Loader.reload_components_module(:my_site) -Beacon.Loader.reload_components_module(:not_booted) -Beacon.Loader.reload_components_module(:booted) -Beacon.Loader.reload_components_module(:s3_site) -Beacon.Loader.reload_components_module(:data_source_test) -Beacon.Loader.reload_components_module(:default_meta_tags_test) -Beacon.Loader.reload_components_module(:lifecycle_test) -Beacon.Loader.reload_components_module(:lifecycle_test_fail) +for site <- [:my_site, :not_booted, :s3_site, :data_source_test, :default_meta_tags_test, :lifecycle_test, :lifecycle_test_fail] do + Beacon.Loader.reload_routes_module(site) + Beacon.Loader.reload_components_module(site) + Beacon.Loader.reload_event_handlers_module(site) +end ExUnit.start(exclude: [:skip]) Ecto.Adapters.SQL.Sandbox.mode(Beacon.BeaconTest.Repo, :manual) From 8f9cafac3f8f06a4a59624436372a93bf0139dfa Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:52:33 -0500 Subject: [PATCH 20/23] reload after fixtures --- test/beacon_web/live/page_live_test.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/beacon_web/live/page_live_test.exs b/test/beacon_web/live/page_live_test.exs index 408221cf..69109a5b 100644 --- a/test/beacon_web/live/page_live_test.exs +++ b/test/beacon_web/live/page_live_test.exs @@ -108,6 +108,7 @@ defmodule Beacon.Web.Live.PageLiveTest do Loader.reload_components_module(:my_site) Loader.reload_layouts_modules(:my_site) Loader.reload_pages_modules(:my_site) + Loader.reload_event_handlers_module(:my_site) [layout: layout] end From 33a4f16f6256af7de265d934f817e1b58aa786e6 Mon Sep 17 00:00:00 2001 From: APB9785 <74077809+APB9785@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:02:05 -0500 Subject: [PATCH 21/23] import Beacon.Web --- lib/beacon/loader/event_handlers.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/beacon/loader/event_handlers.ex b/lib/beacon/loader/event_handlers.ex index 21acb6ac..fdaebeeb 100644 --- a/lib/beacon/loader/event_handlers.ex +++ b/lib/beacon/loader/event_handlers.ex @@ -10,6 +10,8 @@ defmodule Beacon.Loader.EventHandlers do quote do defmodule unquote(module) do + import Beacon.Web, only: [assign: 2, assign: 3, assign_new: 3] + (unquote_splicing(functions)) end end From 9b5c1c6d1879e3e23b8ddc1782ed5d5cfcab7499 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Aug 2024 09:21:57 -0400 Subject: [PATCH 22/23] fix migration --- lib/beacon/migrations/v002.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex index fd59b88a..571f6082 100644 --- a/lib/beacon/migrations/v002.ex +++ b/lib/beacon/migrations/v002.ex @@ -29,7 +29,7 @@ defmodule Beacon.Migrations.V002 do |> Enum.group_by(&{&1.site, &1.name}, & &1.code) |> Enum.map(fn {{site, name}, [code | _]} -> now = DateTime.utc_now() - %{id: Ecto.UUID.generate(), name: name, code: code, site: site, inserted_at: now, updated_at: now} + %{id: Ecto.UUID.generate() |> Ecto.UUID.dump!(), name: name, code: code, site: site, inserted_at: now, updated_at: now} end) |> then(&repo().insert_all("beacon_event_handlers", &1, [])) From 9aa80f633eda15b88e0f634ac8407eb8460c8c83 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Sat, 24 Aug 2024 09:29:34 -0400 Subject: [PATCH 23/23] remove preload now that's loaded in a separate module --- lib/beacon/content.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/beacon/content.ex b/lib/beacon/content.ex index dd315707..a25cd795 100644 --- a/lib/beacon/content.ex +++ b/lib/beacon/content.ex @@ -1025,14 +1025,14 @@ defmodule Beacon.Content do defp extract_page_snapshot(%{schema_version: 1, page: %Page{} = page}) do page |> repo(page).reload() - |> repo(page).preload([:variants, :event_handlers], force: true) + |> repo(page).preload([:variants], force: true) |> maybe_add_leading_slash() end defp extract_page_snapshot(%{schema_version: 2, page: %Page{} = page}) do page |> repo(page).reload() - |> repo(page).preload([:variants, :event_handlers], force: true) + |> repo(page).preload([:variants], force: true) |> maybe_add_leading_slash() end