Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/show nodes #595

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
25 changes: 21 additions & 4 deletions lib/radiator/outline.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ defmodule Radiator.Outline do
# if no previous node is given, the new node will be inserted as the first child of the parent node
def insert_node(%{"show_id" => _show_id} = attrs) do
Repo.transaction(fn ->
prev_id = attrs["prev_id"]
parent_id = attrs["parent_id"]
episode_id = attrs["episode_id"]
prev_id = attrs["prev_id"]
parent_id = convert_parent_id_to_intern(attrs["parent_id"], episode_id)

prev_node = NodeRepository.get_node_if(prev_id)
parent_node = find_parent_node(prev_node, parent_id)
Expand Down Expand Up @@ -116,6 +116,13 @@ defmodule Radiator.Outline do
|> insert_node()
end

defp convert_parent_id_to_intern(nil, episode_id) do
{episode_root, _} = NodeRepository.get_virtual_nodes_for_episode(episode_id)
episode_root.uuid
end

defp convert_parent_id_to_intern(parent_id, _episode_id), do: parent_id

@doc """
Intends a node given by its id (by using the tab key).

Expand Down Expand Up @@ -241,6 +248,12 @@ defmodule Radiator.Outline do
{:error, :parent_and_prev_not_consistent}
end

def move_node(node_id, prev_id: new_prev_id, parent_id: nil) do
node = NodeRepository.get_node(node_id)
intern_parent = convert_parent_id_to_intern(nil, node.episode_id)
move_node(node_id, prev_id: new_prev_id, parent_id: intern_parent)
end

def move_node(node_id, prev_id: new_prev_id, parent_id: new_parent_id) do
case NodeRepository.get_node(node_id) do
nil ->
Expand Down Expand Up @@ -482,10 +495,14 @@ defmodule Radiator.Outline do

Repo.transaction(fn ->
old_next_node =
NodeRepository.get_node_by_parent_and_prev(get_node_id(parent_node), node.uuid)
NodeRepository.get_node_by_parent_and_prev(
get_node_id(parent_node),
node.uuid,
node.episode_id
)

new_next_node =
NodeRepository.get_node_by_parent_and_prev(new_parent_id, new_prev_id)
NodeRepository.get_node_by_parent_and_prev(new_parent_id, new_prev_id, node.episode_id)

{:ok, node} = NodeRepository.move_node_if(node, new_parent_id, new_prev_id)

Expand Down
132 changes: 127 additions & 5 deletions lib/radiator/outline/node_repository.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,95 @@ defmodule Radiator.Outline.NodeRepository do
|> Repo.insert()
end

@doc """
Creates the internal nodes for a show, this is the global root
and the global inbox.
"""
def create_virtual_nodes_for_show(show_id) do
# create a root node for a show
{:ok, show_root} =
create_node(%{
show_id: show_id,
parent_id: nil,
prev_id: nil,
_type: "global_root"
})

{:ok, global_inbox} =
create_node(%{
show_id: show_id,
parent_id: show_root.uuid,
prev_id: nil,
_type: "global_inbox"
})

{show_root, global_inbox}
end

@doc """
Creates the internal nodes for an episode, this is the episode root
and the episode inbox.
"""
def create_virtual_nodes_for_episode(%{id: episode_id, show_id: show_id}) do
# create a root node for a show
{show_root, _global_inbox} = get_virtual_nodes_for_show(show_id)

{:ok, episode_root} =
create_node(%{
episode_id: episode_id,
show_id: show_id,
parent_id: show_root.uuid,
prev_id: nil,
_type: "episode_root"
})

{:ok, episode_inbox} =
create_node(%{
episode_id: episode_id,
show_id: show_id,
parent_id: episode_root.uuid,
prev_id: nil,
_type: "episode_inbox"
})

{episode_root, episode_inbox}
end

@doc """
returns the virtual nodes of a show
TODO: perhaps these should be stored in the show itself?
"""
def get_virtual_nodes_for_show(show_id) do
[node_1, node_2] =
Node
|> where([p], p.show_id == ^show_id)
|> where([p], p._type in [:global_root, :global_inbox])
|> Repo.all()

if node_1._type == :global_root do
{node_1, node_2}
else
{node_2, node_1}
end
end

@doc """
TODO add documentation
"""
def get_virtual_nodes_for_episode(episode_id) do
[node_1, node_2] =
Node
|> where([p], p.episode_id == ^episode_id)
|> where([p], p._type in [:episode_root, :episode_inbox])
|> Repo.all()

if node_1._type == :episode_root do
{node_1, node_2}
else
{node_2, node_1}
end
end

@doc """
Deletes a node from the repository.

Expand Down Expand Up @@ -62,7 +151,7 @@ defmodule Radiator.Outline.NodeRepository do

## Examples

iex> list_nodes(123)
iex> list_nodes_by_episode(123)
[%Node{}, ...]

"""
Expand All @@ -76,6 +165,25 @@ defmodule Radiator.Outline.NodeRepository do
|> List.flatten()
end

@doc """
Returns the list of nodes for a show.

## Examples

iex> _list_nodes_by_show(123)
[%Node{}, ...]

"""

def _list_nodes_by_show(show_id) do
Node
|> where([p], p.show_id == ^show_id)
|> Repo.all()
# |> Enum.group_by(& &1.parent_id)
# |> Enum.map(fn {_parent_id, children} -> Radiator.Outline.order_sibling_nodes(children) end)
|> List.flatten()
end

@doc """
Returns the the number of nodes for an episode

Expand Down Expand Up @@ -150,17 +258,20 @@ defmodule Radiator.Outline.NodeRepository do
end

@doc """
Gets a single node defined by the given prev_id and parent_id.
Gets a single node defined by the given prev_id and parent_id and the
episode id.

Returns `nil` if the Node cannot be found.
## Examples
iex> get_node_by_parent_and_prev("5adf3b360fb0", "380d56cf")
iex> get_node_by_parent_and_prev("5adf3b360fb0", "380d56cf", 23)
nil

iex> get_node_by_parent_and_prev("5e3f5a0422a4", "b78a976d")
iex> get_node_by_parent_and_prev("5e3f5a0422a4", "b78a976d", 23)
%Node{uuid: "33b2a1dac9b1", parent_id: "5e3f5a0422a4", prev_id: "b78a976d"}
"""
def get_node_by_parent_and_prev(parent_id, prev_id) do
def get_node_by_parent_and_prev(parent_id, prev_id, episode_id) do
Node
|> where(episode_id: ^episode_id)
|> where_prev_node_equals(prev_id)
|> where_parent_node_equals(parent_id)
|> Repo.one()
Expand Down Expand Up @@ -273,6 +384,17 @@ defmodule Radiator.Outline.NodeRepository do
|> Repo.one()
end

# @TODO needed?
def get_root_node_for_show(show_id) do
Node
|> where([p], p.show_id == ^show_id)
# |> where([p], is_nil(p.episode_id))
# |> where([p], is_nil(p.parent_id))
# |> where([p], is_nil(p.prev_id))
|> where([p], p._type == :global_root)
|> Repo.one()
end

@doc """
Returns all direct child nodes of a given node.
## Examples
Expand Down
65 changes: 62 additions & 3 deletions lib/radiator/podcast.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import Ecto.Query, warn: false
alias Radiator.Repo

alias Radiator.Outline.NodeRepository
alias Radiator.Podcast.{Episode, Network, Show, ShowHosts}

@doc """
Expand Down Expand Up @@ -169,6 +170,47 @@
|> Repo.preload(preload)
end

@doc """
Gets a single show and preloads its virtual nodes (root and inbox) in a single query.
Sets the virtual attributes root_node_id and inbox_node_id.

## Examples

iex> get_show(123)
%Show{root_node_id: "uuid-1", inbox_node_id: "uuid-2"}

iex> get_show(456)
nil

"""
def get_show(id, preload: preload) do
Show
|> join(:inner, [s], n in Node,
on: n.show_id == s.id and n._type in [:global_root, :global_inbox])
|> where([s, _], s.id == ^id)
|> select([s, n], %{
show: s,
nodes: n
})
|> Repo.all()
|> Repo.preload(preload) # FIXME
|> case do
[] ->
nil
nodes_and_show ->
show = hd(nodes_and_show).show
{root_id, inbox_id} = nodes_and_show
|> Enum.map(& &1.nodes)
|> Enum.reduce({nil, nil}, fn node, {root, inbox} ->
case node._type do

Check warning on line 205 in lib/radiator/podcast.ex

View workflow job for this annotation

GitHub Actions / Build & Test

Function body is nested too deep (max depth is 2, was 3).
:global_root -> {node.uuid, inbox}
:global_inbox -> {root, node.uuid}
end
end)
%{show | root_node_id: root_id, inbox_node_id: inbox_id}
end
end

@doc """
Creates a show.

Expand All @@ -182,9 +224,17 @@

"""
def create_show(attrs \\ %{}) do
%Show{}
|> Show.changeset(attrs)
|> Repo.insert()
# also need to create the nodes for the show
# start a transaction =
Repo.transaction(fn ->
{:ok, show} =
%Show{}
|> Show.changeset(attrs)
|> Repo.insert()

{_show_root, _global_inbox} = NodeRepository.create_virtual_nodes_for_show(show.id)
show
end)
end

@doc """
Expand Down Expand Up @@ -380,6 +430,15 @@
order_by: [desc: e.number]
end

@doc """
Lists all episodes for a given show.
"""
def list_episodes_for_show(show_id) do
list_available_episodes_query()
|> where([e], e.show_id == ^show_id)
|> Repo.all()
end

@doc """
Returns the query for list of episodes including the (soft) deleted once.

Expand Down
2 changes: 2 additions & 0 deletions lib/radiator/podcast/episode.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ defmodule Radiator.Podcast.Episode do
field :slug, :string
field :is_deleted, :boolean, default: false
field :deleted_at, :utc_datetime
field :root_node_id, :binary_id, virtual: true
field :inbox_node_id, :binary_id, virtual: true

belongs_to :show, Show

Expand Down
3 changes: 2 additions & 1 deletion lib/radiator/podcast/show.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ defmodule Radiator.Podcast.Show do
schema "shows" do
field :title, :string
field :description, :string
field :root_node_id, :binary_id, virtual: true
field :inbox_node_id, :binary_id, virtual: true

belongs_to :network, Network

has_many(:episodes, Episode)
has_many(:outline_nodes, Node)
many_to_many(:hosts, User, join_through: "show_hosts")
Expand Down
17 changes: 17 additions & 0 deletions test/radiator/outline_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,23 @@ defmodule Radiator.OutlineTest do
assert NodeRepository.get_node!(nested_node_2.uuid).prev_id == nested_node_1.uuid
end

test "when parent node is nil it will be set to the episode root", %{
parent_node: parent_node
} do
node_attrs = %{"parent_id" => nil, "episode_id" => parent_node.episode_id}
{:ok, %{node: new_node}} = Outline.insert_node(node_attrs)
# do not test result since it will be altered
# but fetching if from the repo with low level functions will return the true value
{episode_root, _} = NodeRepository.get_virtual_nodes_for_episode(parent_node.episode_id)
assert NodeRepository.get_node!(new_node.uuid).parent_id == episode_root.uuid
end

test "when parent node is not given it will be set to the episode root", %{
parent_node: parent_node
} do

end

test "without a parent node the inserted node will be put at the top", %{
parent_node: parent_node
} do
Expand Down
Loading
Loading