Skip to content

Commit

Permalink
implement move_up, move_down (#571)
Browse files Browse the repository at this point in the history
  • Loading branch information
electronicbites authored Sep 25, 2024
1 parent dda1f5b commit d9e75cf
Show file tree
Hide file tree
Showing 14 changed files with 361 additions and 96 deletions.
6 changes: 3 additions & 3 deletions lib/radiator/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Radiator.Application do
use Application

alias Radiator.Outline.CommandProcessor
alias Radiator.Outline.EventProducer
alias Radiator.Outline.CommandQueue
alias Radiator.Outline.NodeChangeListener

@impl true
Expand All @@ -22,8 +22,8 @@ defmodule Radiator.Application do
# {Radiator.Worker, arg},
# Start to serve requests, typically the last entry
RadiatorWeb.Endpoint,
{EventProducer, name: EventProducer},
{CommandProcessor, name: CommandProcessor, subscribe_to: [{EventProducer, max_demand: 1}]},
{CommandQueue, name: CommandQueue},
{CommandProcessor, name: CommandProcessor, subscribe_to: [{CommandQueue, max_demand: 1}]},
{NodeChangeListener, name: NodeChangeListener}
]

Expand Down
142 changes: 134 additions & 8 deletions lib/radiator/outline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,7 @@ defmodule Radiator.Outline do
parent_node = find_parent_node(prev_node, parent_id)

# find Node which has been previously connected to prev_node
next_node =
Node
|> where(episode_id: ^episode_id)
|> where_prev_node_equals(prev_id)
|> where_parent_node_equals(get_node_id(parent_node))
|> Repo.one()
next_node = get_next_node(episode_id, prev_id, get_node_id(parent_node))

with true <- parent_and_prev_consistent?(parent_node, prev_node),
true <- episode_valid?(episode_id, parent_node, prev_node),
Expand All @@ -109,6 +104,18 @@ defmodule Radiator.Outline do
end)
end

@doc """
Intends a node given by its id (by using the tab key).
## Examples
iex> indent_node("074b755d-d095-4b9c-8445-ef1f7ea76d54")
{:ok, %NodeRepoResult{}}
iex> indent_node("0000000-1111-2222-3333-44444444")
{:error, :not_found}
"""
def indent_node(node_id) do
Repo.transaction(fn ->
case NodeRepository.get_node(node_id) do
Expand All @@ -123,6 +130,18 @@ defmodule Radiator.Outline do
|> unwrap_transaction_result
end

@doc """
Outdents a node given by its id (by using the shift-tab keys).
## Examples
iex> outdent_node("074b755d-d095-4b9c-8445-ef1f7ea76d54")
{:ok, %NodeRepoResult{}}
iex> outdent_node("0000000-1111-2222-3333-44444444")
{:error, :not_found}
"""
def outdent_node(node_id) do
Repo.transaction(fn ->
case NodeRepository.get_node(node_id) do
Expand All @@ -137,6 +156,59 @@ defmodule Radiator.Outline do
|> unwrap_transaction_result
end

@doc """
Moves a node up in the outline tree. Only works if the node
is not the first child of its parent meaning there must be a
previous node. In that case the two nodes will switch places.
## Examples
iex> move_up("074b755d-d095-4b9c-8445-ef1f7ea76d54")
{:ok, %NodeRepoResult{}}
iex> move_up("0000000-1111-2222-3333-44444444")
{:error, :not_found}
"""
def move_up(node_id) do
Repo.transaction(fn ->
case NodeRepository.get_node(node_id) do
nil ->
{:error, :not_found}

node ->
prev_node = get_prev_node(node)
do_move_up(node, prev_node)
end
end)
|> unwrap_transaction_result
end

@doc """
Moves a node down in the outline tree. Only works if the node
is not the last child of its parent meaning there must be a next node.
In that case the two nodes will switch places.
## Examples
iex> move_down("074b755d-d095-4b9c-8445-ef1f7ea76d54")
{:ok, %NodeRepoResult{}}
iex> move_down("0000000-1111-2222-3333-44444444")
{:error, :not_found}
"""
def move_down(node_id) do
Repo.transaction(fn ->
case NodeRepository.get_node(node_id) do
nil ->
{:error, :not_found}

node ->
next_node = get_next_node(node)
do_move_down(node, next_node)
end
end)
|> unwrap_transaction_result
end

@doc """
Moves a node to another parent.
Expand Down Expand Up @@ -275,14 +347,26 @@ defmodule Radiator.Outline do
"""
def get_prev_node(nil), do: nil
def get_prev_node(node) when is_nil(node.prev_id), do: nil
def get_prev_node(%Node{prev_id: nil}), do: nil

def get_prev_node(node) do
def get_prev_node(%Node{} = node) do
Node
|> where([n], n.uuid == ^node.prev_id)
|> Repo.one()
end

def get_next_node(%Node{episode_id: episode_id, uuid: node_id, parent_id: parent_id}) do
get_next_node(episode_id, node_id, parent_id)
end

def get_next_node(episode_id, node_id, parent_id) do
Node
|> where(episode_id: ^episode_id)
|> where_prev_node_equals(node_id)
|> where_parent_node_equals(parent_id)
|> Repo.one()
end

@doc """
Returns the parent node of a given node in the outline tree.
Returns `nil` if parent_id of the node is nil.
Expand Down Expand Up @@ -538,6 +622,46 @@ defmodule Radiator.Outline do
{:ok, Map.put(main_move_result, :children, new_children)}
end

defp do_move_up(%Node{}, nil), do: {:error, :no_previous_node}

defp do_move_up(
%Node{episode_id: episode_id, parent_id: parent_id} = node,
%Node{} = prev_node
) do
next_node = get_next_node(episode_id, node.uuid, parent_id)

move_node_if(node, parent_id, prev_node.prev_id)
move_node_if(prev_node, parent_id, node.uuid)
move_node_if(next_node, parent_id, prev_node.uuid)

%NodeRepoResult{
node: get_node_result_info(node),
episode_id: episode_id,
old_prev: get_node_result_info(prev_node),
old_next: get_node_result_info(next_node)
}
end

defp do_move_down(%Node{}, nil), do: {:error, :no_next_node}

defp do_move_down(
%Node{episode_id: episode_id, parent_id: parent_id} = node,
%Node{} = next_node
) do
new_next_node = get_next_node(next_node)

move_node_if(next_node, parent_id, node.prev_id)
move_node_if(node, parent_id, next_node.uuid)
move_node_if(new_next_node, parent_id, node.uuid)

%NodeRepoResult{
node: get_node_result_info(node),
episode_id: episode_id,
old_next: get_node_result_info(next_node),
next: get_node_result_info(new_next_node)
}
end

# given a list of nodes in one level, return all the nodes that are after a give
defp next_nodes([], _node), do: []
defp next_nodes([%{prev_id: uuid} | _tail] = children, %{uuid: uuid}), do: children
Expand Down Expand Up @@ -582,6 +706,8 @@ defmodule Radiator.Outline do
{:error, error}
end

defp unwrap_transaction_result(result), do: result

defp binaray_uuid_to_ecto_uuid(nil), do: nil

defp binaray_uuid_to_ecto_uuid(uuid) do
Expand Down
28 changes: 28 additions & 0 deletions lib/radiator/outline/command.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@ defmodule Radiator.Outline.Command do
DeleteNodeCommand,
IndentNodeCommand,
InsertNodeCommand,
MoveDownCommand,
MoveNodeCommand,
MoveUpCommand,
OutdentNodeCommand
}

@move_commands [
IndentNodeCommand,
MoveDownCommand,
MoveNodeCommand,
MoveUpCommand,
OutdentNodeCommand
]

defguard move_command?(command) when command in @move_commands

def build("insert_node", payload, user_id, event_id) do
%InsertNodeCommand{
event_id: event_id,
Expand Down Expand Up @@ -42,6 +54,22 @@ defmodule Radiator.Outline.Command do
}
end

def build("move_up", node_id, user_id, event_id) do
%MoveUpCommand{
event_id: event_id,
user_id: user_id,
node_id: node_id
}
end

def build("move_down", node_id, user_id, event_id) do
%MoveDownCommand{
event_id: event_id,
user_id: user_id,
node_id: node_id
}
end

def build("change_node_content", node_id, content, user_id, event_id) do
%ChangeNodeContentCommand{
event_id: event_id,
Expand Down
12 changes: 12 additions & 0 deletions lib/radiator/outline/command/move_down_command.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule Radiator.Outline.Command.MoveDownCommand do
@moduledoc """
Command to move a node down inside one level of the outline.
"""
@type t() :: %__MODULE__{
event_id: binary(),
user_id: binary(),
node_id: binary()
}

defstruct [:event_id, :user_id, :node_id]
end
2 changes: 1 addition & 1 deletion lib/radiator/outline/command/move_node_command.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule Radiator.Outline.Command.MoveNodeCommand do
@moduledoc """
Command to move a nodeinside the outline to another place.
Command to move a node inside the outline to another place.
"""
@type t() :: %__MODULE__{
event_id: binary(),
Expand Down
12 changes: 12 additions & 0 deletions lib/radiator/outline/command/move_up_command.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule Radiator.Outline.Command.MoveUpCommand do
@moduledoc """
Command to move a node up inside one level of the outline.
"""
@type t() :: %__MODULE__{
event_id: binary(),
user_id: binary(),
node_id: binary()
}

defstruct [:event_id, :user_id, :node_id]
end
63 changes: 21 additions & 42 deletions lib/radiator/outline/command_processor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ defmodule Radiator.Outline.CommandProcessor do
alias Radiator.Outline
alias Radiator.Outline.NodeRepoResult

alias Radiator.Outline.Command

alias Radiator.Outline.Command.{
ChangeNodeContentCommand,
DeleteNodeCommand,
IndentNodeCommand,
InsertNodeCommand,
MoveDownCommand,
MoveNodeCommand,
MoveUpCommand,
OutdentNodeCommand
}

Expand All @@ -28,6 +32,8 @@ defmodule Radiator.Outline.CommandProcessor do
alias Radiator.Outline.NodeRepository

require Logger
# for the guard
require Radiator.Outline.Command

def start_link(opts \\ []) do
{name, opts} = Keyword.pop(opts, :name, __MODULE__)
Expand Down Expand Up @@ -82,6 +88,18 @@ defmodule Radiator.Outline.CommandProcessor do
|> handle_move_node_result(command)
end

defp process_command(%MoveUpCommand{node_id: node_id} = command) do
node_id
|> Outline.move_up()
|> handle_move_node_result(command)
end

defp process_command(%MoveDownCommand{node_id: node_id} = command) do
node_id
|> Outline.move_down()
|> handle_move_node_result(command)
end

defp process_command(%DeleteNodeCommand{node_id: node_id} = command) do
case NodeRepository.get_node(node_id) do
nil ->
Expand Down Expand Up @@ -129,48 +147,9 @@ defmodule Radiator.Outline.CommandProcessor do

def handle_move_node_result(
{:ok, %NodeRepoResult{node: node} = result},
%MoveNodeCommand{} = command
) do
%NodeMovedEvent{
node: Outline.get_node_result_info(node),
old_prev: result.old_prev,
old_next: result.old_next,
user_id: command.user_id,
event_id: command.event_id,
next: result.next,
episode_id: result.episode_id,
children: result.children
}
|> EventStore.persist_event()
|> Dispatch.broadcast()

{:ok, node}
end

def handle_move_node_result(
{:ok, %NodeRepoResult{node: node} = result},
%IndentNodeCommand{} = command
) do
%NodeMovedEvent{
node: node,
old_prev: result.old_prev,
old_next: result.old_next,
user_id: command.user_id,
event_id: command.event_id,
next: result.next,
episode_id: result.episode_id,
children: result.children
}
|> EventStore.persist_event()
|> Dispatch.broadcast()

{:ok, node}
end

def handle_move_node_result(
{:ok, %NodeRepoResult{node: node} = result},
%OutdentNodeCommand{} = command
) do
%command_type{} = command
)
when Command.move_command?(command_type) do
%NodeMovedEvent{
node: node,
old_prev: result.old_prev,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Radiator.Outline.EventProducer do
defmodule Radiator.Outline.CommandQueue do
@moduledoc false

use GenStage
Expand Down
Loading

0 comments on commit d9e75cf

Please sign in to comment.