diff --git a/lib/vyasa/sangh.ex b/lib/vyasa/sangh.ex index e6df6301..67579a89 100644 --- a/lib/vyasa/sangh.ex +++ b/lib/vyasa/sangh.ex @@ -6,9 +6,12 @@ defmodule Vyasa.Sangh do import Ecto.Query, warn: false import EctoLtree.Functions, only: [nlevel: 1] alias Vyasa.Repo + alias EctoLtree.LabelTree, as: Ltree # alias Vyasa.Draft alias Vyasa.Sangh.{Sheaf} + @max_sheaf_depth 3 + @doc """ Returns a list of sheafs associated with a specific session. @@ -403,11 +406,117 @@ defmodule Vyasa.Sangh do # """ def update_sheaf(%Sheaf{} = sheaf, attrs) do + IO.puts("CHECKPOINT: update_sheaf") + sheaf |> Sheaf.mutate_changeset(attrs) |> Repo.update() end + @doc """ + Given a sheaf that is ready to be published as a reply, ensures that the + replies adhere to the 3-level nesting limit. + + There are 3 ways that the reply to context is determined: + 1. if the attrs contain :parent, then use that sheaf. This is more of an override-case, + for example, in the context of quick replies + 2. if the attrs don't contain parent, but the draft sheaf already has an associated parent, + then that's the reply to context + 3. there are no parents associated -- so the reply's parent is nil and there's no :parent in the attrs + + Suppose the reply to context is already at depth = 3 (zero-indexed, then level = 2) then we need to reconcile this. + To reconcile, we shall update the attrs payload such that: + 1. the parent of the reply sheaf ends up being the grandparent instead + 2. the reply sheaf's path gets updated, is now encoded as though its the child of its grandparent + """ + # way number 1 -- using parent in attr + def make_reply( + %Sheaf{ + id: id + } = reply, + %{ + parent: + %Sheaf{ + parent_id: grandparent_id, + path: %Ltree{ + labels: parent_path_labels + } + } = _injected_parent + } = attrs + ) do + IO.puts("CHECKPOINT Make Reply way 1 -- use injected parent in attr") + + reconciled_attrs = + case parent_path_labels |> Enum.count() do + # need to reassign both path and parent: + @max_sheaf_depth -> + attrs |> reconcile_payload_using_grandparent(grandparent_id, id) + + # keep as is + _ -> + attrs + end + + reply |> update_sheaf(reconciled_attrs) + end + + # way number 2 -- if not in attr, then use pre-existing associated parent to the reply sheaf + def make_reply( + %Sheaf{ + id: id, + parent: %Sheaf{ + parent_id: grandparent_id, + path: %Ltree{ + labels: parent_path_labels + } + } + } = reply, + attrs + ) do + IO.puts("CHECKPOINT Make Reply way 2 -- use pre-associated parent") + + reconciled_attrs = + case parent_path_labels |> Enum.count() do + # need to reassign both path and parent: + @max_sheaf_depth -> + attrs |> reconcile_payload_using_grandparent(grandparent_id, id) + + _ -> + attrs + end + + reply |> update_sheaf(reconciled_attrs) + end + + # way number 3 -- no assoced parent, nothing to reconcile + def make_reply( + %Sheaf{ + parent: nil + } = reply, + attrs + ) do + IO.puts("CHECKPOINT Make Reply way 3 -- no parent to associate to") + reply |> update_sheaf(attrs) + end + + # To reconcile, we shall update the attrs payload such that: + # 1. the parent of the reply sheaf ends up being the grandparent instead + # 2. the reply sheaf's path gets updated, is now encoded as though its the child of its grandparent + defp reconcile_payload_using_grandparent(%{} = attrs, grandparent_id, child_id) + when is_binary(grandparent_id) do + %Sheaf{path: %Ltree{} = grandparent_path} = grandparent = get_sheaf!(grandparent_id) + + IO.puts( + "CHECKPOINT -- turns out we need to reconcile this sheaf and assoc to its grandparent instead" + ) + + attrs + |> Map.merge(%{ + parent: grandparent, + path: child_id |> Sheaf.encode_path(grandparent_path) + }) + end + # @doc """ # Updates a sheaf. @@ -421,6 +530,8 @@ defmodule Vyasa.Sangh do # """ def update_sheaf!(%Sheaf{} = sheaf, attrs) do + IO.puts("CHECKPOINT: update_sheaf!") + sheaf |> Sheaf.mutate_changeset(attrs) |> Repo.update!() diff --git a/lib/vyasa/sangh/sheaf.ex b/lib/vyasa/sangh/sheaf.ex index 759ce83b..a1f7a58a 100644 --- a/lib/vyasa/sangh/sheaf.ex +++ b/lib/vyasa/sangh/sheaf.ex @@ -67,13 +67,19 @@ defmodule Vyasa.Sangh.Sheaf do end def mutate_changeset(%Sheaf{} = sheaf, attrs) do - sheaf - |> Vyasa.Repo.preload([:marks]) - |> cast(attrs, [:id, :body, :active, :signature, :traits]) - |> cast_path(attrs) - |> assoc_marks(attrs) - |> Map.put(:repo_opts, on_conflict: {:replace_all_except, [:id]}, conflict_target: :id) - |> validate_include_subset(:traits, ["personal", "draft", "published"]) + IO.inspect(%{sheaf: sheaf, attrs: attrs}, label: "CHECKPOINT: mutate_changeset") + + cs = + sheaf + |> Vyasa.Repo.preload([:marks]) + |> cast(attrs, [:id, :body, :active, :signature, :traits, :updated_at, :inserted_at]) + |> cast_path(attrs) + |> assoc_marks(attrs) + |> Map.put(:repo_opts, on_conflict: {:replace_all_except, [:id]}, conflict_target: :id) + |> validate_include_subset(:traits, ["personal", "draft", "published"]) + + IO.inspect(cs, label: "CHECKPOINT: mutate changeset outcome changeset:") + cs end defp assoc_marks(sheaf, %{marks: [%Mark{} | _] = marks}) do @@ -87,17 +93,25 @@ defmodule Vyasa.Sangh.Sheaf do end defp cast_path(%{changes: %{id: sheaf_id}} = sheaf, %{ - parent: %Sheaf{id: p_sheaf_id, path: lpath} + parent: %Sheaf{id: p_sheaf_id, path: lpath} = parent }) do + IO.inspect(parent, label: "SEE ME : cast_path, parent:") + sheaf |> cast(%{parent_id: p_sheaf_id, path: encode_path(sheaf_id, lpath)}, [:parent_id, :path]) end defp cast_path(%{changes: %{id: sheaf_id}} = sheaf, _) do + IO.inspect(sheaf, label: "SEE ME : cast_path, sheaf:") + sheaf |> cast(%{path: encode_path(sheaf_id)}, [:path]) end + defp cast_path(%{data: %{id: sheaf_id}} = sheaf, %{parent: %Sheaf{id: p_sheaf_id, path: lpath}}) do + cast(sheaf, %{parent_id: p_sheaf_id, path: encode_path(sheaf_id, lpath)}, [:parent_id, :path]) + end + defp cast_path(sheaf, _) do sheaf end @@ -204,4 +218,18 @@ defmodule Vyasa.Sangh.Sheaf do ) do labels end + + @doc """ + For sorting sheafs, we should just default to only caring about their insertion time, + ignoring the update time. + + TODO: update clauses once we are supporting other kinds of ordering + """ + def sort_sheafs_chrono([%Sheaf{} | _] = sheafs) do + Enum.sort_by(sheafs, &elem(&1, 1).inserted_at, :desc) + end + + def sort_sheaf_chrono(sheafs) do + sheafs + end end diff --git a/lib/vyasa/sangh/sheaf_lattice.ex b/lib/vyasa/sangh/sheaf_lattice.ex index b5433d79..2eba336b 100644 --- a/lib/vyasa/sangh/sheaf_lattice.ex +++ b/lib/vyasa/sangh/sheaf_lattice.ex @@ -323,6 +323,8 @@ defmodule Vyasa.Sangh.SheafLattice do sheaf_lattice |> read_sheaf_lattice(level, match) |> Enum.reject(fn %Sheaf{traits: traits} -> "draft" in traits end) + # TODO: to verify this, may not be correct + |> sort_sheaf_lattice_entries_chrono() end # fetches all sheafs in level 0: @@ -449,4 +451,12 @@ defmodule Vyasa.Sangh.SheafLattice do lattice |> update_sheaf_in_lattice(lattice_key, %Sheaf{old_sheaf | marks: updated_marks}) end + + def sort_sheaf_lattice_entries_chrono([{label, %Sheaf{}} | _] = entries) when is_list(label) do + entries |> Enum.sort_by(fn {_, %Sheaf{} = sheaf} -> sheaf.inserted_at end, :desc) + end + + def sort_sheaf_lattice_entries_chrono(entries) do + entries + end end diff --git a/lib/vyasa_web/components/contexts/components.ex b/lib/vyasa_web/components/contexts/components.ex index 79ed0277..48a6d1cb 100644 --- a/lib/vyasa_web/components/contexts/components.ex +++ b/lib/vyasa_web/components/contexts/components.ex @@ -4,6 +4,7 @@ defmodule VyasaWeb.Context.Components do """ use VyasaWeb, :html alias Vyasa.Sangh.{Sheaf} + alias VyasaWeb.CoreComponents alias VyasaWeb.Context.Components.UiState.Mark, as: MarkUiState alias VyasaWeb.Context.Components.UiState.Sheaf, as: SheafUiState @@ -323,7 +324,7 @@ defmodule VyasaWeb.Context.Components do
<.form for={%{}} - phx-submit={JS.push("sheaf::publish")} + phx-submit={CoreComponents.hide_modal(JS.push("sheaf::publish"), @id)} phx-target={@event_target} class="flex items-center" > @@ -377,7 +378,7 @@ defmodule VyasaWeb.Context.Components do