Skip to content

Commit

Permalink
Save state
Browse files Browse the repository at this point in the history
  • Loading branch information
APB9785 committed Dec 11, 2023
1 parent ac308ae commit f366c83
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 55 deletions.
101 changes: 70 additions & 31 deletions lib/beacon/content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ defmodule Beacon.Content do
alias Beacon.Content.LayoutEvent
alias Beacon.Content.LayoutSnapshot
alias Beacon.Content.LiveData
alias Beacon.Content.LiveDataAssign
alias Beacon.Content.Page
alias Beacon.Content.PageEvent
alias Beacon.Content.PageEventHandler
Expand Down Expand Up @@ -2039,29 +2040,29 @@ defmodule Beacon.Content do
# LIVE DATA

@doc """
Returns a list of all existing LiveData formats.
Returns a list of all existing LiveDataAssign formats.
"""
@doc type: :live_data
@spec live_data_formats() :: [atom()]
def live_data_formats, do: LiveData.formats()
@spec live_data_assign_formats() :: [atom()]
def live_data_assign_formats, do: LiveDataAssign.formats()

@doc """
Returns an `%Ecto.Changeset{}` for tracking LiveData changes.
Returns an `%Ecto.Changeset{}` for tracking LiveData `:path` changes.
## Example
iex> change_live_data(live_data, %{code: "false"})
iex> change_live_data(live_data, %{path: "/foo/:bar_id"})
%Ecto.Changeset{data: %LiveData{}}
"""
@doc type: :live_data
@spec change_live_data(LiveData.t(), map()) :: Changeset.t()
def change_live_data(%LiveData{} = live_data, attrs \\ %{}) do
LiveData.changeset(live_data, attrs)
@spec change_live_data_path(LiveData.t(), map()) :: Changeset.t()
def change_live_data_path(%LiveData{} = live_data, attrs \\ %{}) do
LiveData.path_changeset(live_data, attrs)
end

@doc """
Creates new assigns for Page templates using user-inputted code.
Creates a new LiveData for scoping live data to pages.
"""
@doc type: :live_data
@spec create_live_data(map()) :: {:ok, LiveData.t()} | {:error, Changeset.t()}
Expand All @@ -2073,18 +2074,40 @@ defmodule Beacon.Content do
end

@doc """
Gets a single LiveData entry by `id`.
Creates a new LiveDataAssign.
"""
@doc type: :live_data
@spec create_assign_for_live_data(LiveData.t(), map()) :: {:ok, LiveData.t()} | {:error, Changeset.t()}
def create_assign_for_live_data(live_data, attrs) do
changeset =
live_data
|> Ecto.build_assoc(:assigns)
|> LiveDataAssign.changeset(attrs)

case Repo.insert(changeset) do
{:ok, %LiveDataAssign{}} ->
live_data = Repo.preload(live_data, :assigns, force: true)
maybe_reload_live_data({:ok, live_data})
{:ok, live_data}

{:error, changeset} ->
{:error, changeset}
end
end

@doc """
Gets a single `LiveData` entry by `:site` and `:path`.
## Example
iex> get_live_data("788b2161-b23a-48ed-abcd-8af788004bbb")
iex> get_live_data(:my_site, "/foo/bar/:baz")
%LiveData{}
"""
@doc type: :live_data
@spec get_live_data(Ecto.UUID.t()) :: LiveData.t() | nil
def get_live_data(id) when is_binary(id) do
Repo.get(LiveData, id)
@spec get_live_data(Site.t(), String.t()) :: LiveData.t() | nil
def get_live_data(site, path) do
Repo.get_by(LiveData, site: site, path: path)
end

@doc """
Expand All @@ -2093,11 +2116,11 @@ defmodule Beacon.Content do
@doc type: :live_data
@spec live_data_for_site(Site.t()) :: [LiveData.t()]
def live_data_for_site(site) do
Repo.all(from ld in LiveData, where: ld.site == ^site)
Repo.all(from ld in LiveData, where: ld.site == ^site, preload: :assigns)
end

@doc """
Query paths with LiveData for a given site.
Query LiveData paths for a given site.
## Options
Expand All @@ -2108,7 +2131,7 @@ defmodule Beacon.Content do
@doc type: :live_data
@spec live_data_paths_for_site(Site.t(), Keyword.t()) :: [String.t()]
def live_data_paths_for_site(site, opts \\ []) do
per_page = Keyword.get(opts, :per_page, 20)
per_page = Keyword.get(opts, :per_page, :infinity)
search = Keyword.get(opts, :query)

site
Expand All @@ -2122,7 +2145,6 @@ defmodule Beacon.Content do
from ld in LiveData,
where: ld.site == ^site,
select: ld.path,
distinct: ld.path,
order_by: [asc: ld.path]
end

Expand All @@ -2133,36 +2155,44 @@ defmodule Beacon.Content do
defp query_live_data_paths_for_site_search(query, _search), do: query

@doc """
Gets all LiveData for a single path of a given site.
Updates LiveDataPath.
iex> update_live_data_path(live_data, "/foo/bar/:baz_id")
{:ok, %LiveData{}}
"""
@doc type: :live_data
@spec live_data_for_path(Site.t(), String.t()) :: [LiveData.t()]
def live_data_for_path(site, path) do
Repo.all(from ld in LiveData, where: ld.path == ^path and ld.site == ^site)
@spec update_live_data_path(LiveData.t(), String.t()) :: {:ok, LiveData.t()} | {:error, Changeset.t()}
def update_live_data_path(%LiveData{} = live_data, path) do
live_data
|> LiveData.path_changeset(%{path: path})
|> Repo.update()
|> tap(&maybe_reload_live_data/1)
end

@doc """
Updates LiveData.
Updates LiveDataAssign.
iex> update_live_data(live_data, %{code: "true"})
{:ok, %LiveData{}}
iex> update_live_data_assign(live_data_assign, %{code: "true"})
{:ok, %LiveDataAssign{}}
"""
@doc type: :live_data
@spec update_live_data(LiveData.t(), map()) :: {:ok, LiveData.t()} | {:error, Changeset.t()}
def update_live_data(%LiveData{} = live_data, attrs) do
live_data
|> LiveData.changeset(attrs)
@spec update_live_data_assign(LiveDataAssign.t(), map()) :: {:ok, LiveDataAssign.t()} | {:error, Changeset.t()}
def update_live_data_assign(%LiveDataAssign{} = live_data_assign, attrs) do
live_data_assign
|> Repo.preload(:live_data)
|> LiveDataAssign.changeset(attrs)
|> validate_live_data_code()
|> Repo.update()
|> tap(&maybe_reload_live_data/1)
end

defp validate_live_data_code(changeset) do
site = Changeset.get_field(changeset, :site)
code = Changeset.get_field(changeset, :code)
value = Changeset.get_field(changeset, :value)
metadata = %Beacon.Template.LoadMetadata{site: site, path: "nopath"}
do_validate_template(changeset, :code, :heex, code, metadata)
do_validate_template(changeset, :value, :heex, value, metadata)
end

def maybe_reload_live_data({:ok, live_data}), do: PubSub.live_data_updated(live_data)
Expand All @@ -2177,6 +2207,15 @@ defmodule Beacon.Content do
Repo.delete(live_data)
end

@doc """
Deletes LiveDataAssign.
"""
@doc type: :live_data
@spec delete_live_data_assign(LiveDataAssign.t()) :: {:ok, LiveDataAssign.t()} | {:error, Changeset.t()}
def delete_live_data_assign(live_data_assign) do
Repo.delete(live_data_assign)
end

## Utils

defp do_validate_template(changeset, field, _format, nil = _template, _metadata) do
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule Beacon.Content.LiveDataPath do
defmodule Beacon.Content.LiveData do
@moduledoc """
Dynamic assigns to be used by page templates and updated with page event handlers.
The LiveData schema scopes `LiveDataAssign`s to `Page`s via site and path.
> #### Do not create or edit live data manually {: .warning}
>
Expand All @@ -20,29 +20,27 @@ defmodule Beacon.Content.LiveDataPath do
id: Ecto.UUID.t(),
site: Beacon.Types.Site.t(),
path: String.t(),
live_data_assigns: [LiveDataAssign]
assigns: [LiveDataAssign]
}

@formats [:text, :elixir]

schema "beacon_live_data" do
field :site, Beacon.Types.Site
field :path, :string

has_many :live_data_assigns, LiveDataAssign
has_many :assigns, LiveDataAssign

timestamps()
end

def changeset(%__MODULE__{} = live_data_path, attrs) do
def changeset(%__MODULE__{} = live_data, attrs) do
fields = ~w(site path)a

live_data
|> cast(attrs, fields)
|> validate_required(fields)
end

def path_changeset(%__MODULE__{} = live_data_path, attrs) do
def path_changeset(%__MODULE__{} = live_data, attrs) do
live_data
|> cast(attrs, [:path])
|> validate_required([:path])
Expand Down
18 changes: 9 additions & 9 deletions lib/beacon/content/live_data_assign.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,31 @@ defmodule Beacon.Content.LiveDataAssign do

use Beacon.Schema

alias Beacon.Content.LiveDataPath
alias Beacon.Content.LiveData

@type t :: %__MODULE__{
id: Ecto.UUID.t(),
live_data_path_id: Ecto.UUID.t(),
live_data_path: LiveDataPath.t(),
assign: String.t(),
key: String.t(),
value: String.t(),
format: :text | :elixir,
code: String.t()
live_data_id: Ecto.UUID.t(),
live_data: LiveData.t()
}

@formats [:text, :elixir]

schema "beacon_live_data_assigns" do
field :assign, :string
field :key, :string
field :value, :string
field :format, Ecto.Enum, values: @formats
field :code, :string

belongs_to :live_data_path, LiveDataPath
belongs_to :live_data, LiveData

timestamps()
end

def changeset(%__MODULE__{} = live_data_assign, attrs) do
fields = ~w(assign format code live_data_path_id)a
fields = ~w(key value format live_data_id)a

live_data_assign
|> cast(attrs, fields)
Expand Down
9 changes: 9 additions & 0 deletions lib/beacon/pub_sub.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Beacon.PubSub do
alias Beacon.Content.ErrorPage
alias Beacon.Content.Layout
alias Beacon.Content.LiveData
alias Beacon.Content.LiveDataAssign
alias Beacon.Content.Page

@pubsub __MODULE__
Expand Down Expand Up @@ -172,6 +173,14 @@ defmodule Beacon.PubSub do
|> broadcast(:live_data_updated)
end

def live_data_updated(%LiveDataAssign{} = live_data_assign) do
%{live_data: %{site: site}} = Beacon.Repo.preload(live_data_assign, :live_data)

site
|> topic_live_data()
|> broadcast(:live_data_updated)
end

defp topic_live_data(site), do: "beacon:#{site}:live_data"

# Utils
Expand Down
3 changes: 0 additions & 3 deletions priv/repo/migrations/20231006223956_create_live_data.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ defmodule Beacon.Repo.Migrations.CreateLiveData do
add :id, :binary_id, primary_key: true
add :site, :text, null: false
add :path, :text, null: false
add :assign, :text, null: false
add :code, :text, null: false
add :format, :string, null: false

timestamps()
end
Expand Down
17 changes: 17 additions & 0 deletions priv/repo/migrations/20231211162418_create_live_data_assigns.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule Beacon.Repo.Migrations.CreateLiveDataAssigns do
use Ecto.Migration

def change do
create table(:beacon_live_data_assigns, primary_key: false) do
add :id, :binary_id, primary_key: true
add :key, :text, null: false
add :value, :text, null: false
add :format, :string, null: false
add :live_data_id, references(:beacon_live_data, on_delete: :delete_all, type: :binary_id), null: false

timestamps()
end

create index(:beacon_live_data_assigns, [:live_data_id])
end
end
Loading

0 comments on commit f366c83

Please sign in to comment.