From b0ffab2298238823ce2812df5e2fc3fa9da3715d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Wo=CC=88ginger?= Date: Sat, 5 Oct 2024 13:08:30 +0200 Subject: [PATCH] delete all urls of a node before creating new ones --- lib/radiator/outline/node.ex | 2 + lib/radiator/resources.ex | 44 ++++++++++- lib/radiator/resources/url_worker.ex | 2 +- test/radiator/resources/url_worker_test.exs | 32 ++++++++ test/radiator/resources_test.exs | 86 +++++++++++++++------ test/support/fixtures/resources_fixtures.ex | 4 +- 6 files changed, 143 insertions(+), 27 deletions(-) create mode 100644 test/radiator/resources/url_worker_test.exs diff --git a/lib/radiator/outline/node.ex b/lib/radiator/outline/node.ex index afa6b46b..be544ecf 100644 --- a/lib/radiator/outline/node.ex +++ b/lib/radiator/outline/node.ex @@ -5,6 +5,7 @@ defmodule Radiator.Outline.Node do use Ecto.Schema import Ecto.Changeset alias Radiator.Podcast.Episode + alias Radiator.Resources.Url @derive {Jason.Encoder, only: [:uuid, :content, :creator_id, :parent_id, :prev_id]} @@ -17,6 +18,7 @@ defmodule Radiator.Outline.Node do field :level, :integer, virtual: true belongs_to :episode, Episode + has_many :urls, Url, foreign_key: :node_id timestamps(type: :utc_datetime) end diff --git a/lib/radiator/resources.ex b/lib/radiator/resources.ex index b209b298..f1d0c6bc 100644 --- a/lib/radiator/resources.ex +++ b/lib/radiator/resources.ex @@ -6,6 +6,7 @@ defmodule Radiator.Resources do import Ecto.Query, warn: false + alias Radiator.Outline.Node alias Radiator.Repo alias Radiator.Resources.Url @@ -16,11 +17,11 @@ defmodule Radiator.Resources do ## Examples - iex> list_urls(episode_id) + iex> list_urls_of_episode(episode_id) [%Url{}, ...] """ - def list_urls(episode_id) do + def list_urls_of_episode(episode_id) do query = from u in Url, join: n in assoc(u, :node), @@ -45,6 +46,28 @@ defmodule Radiator.Resources do """ def get_url!(id), do: Repo.get!(Url, id) + @doc """ + creates all URL entities for a node. Before all existing URLs for this + node get deleted. + """ + def rebuild_node_urls(node_id, url_attributes) do + {:ok, created_urls} = + Repo.transaction(fn -> + _number_of_nodes = delete_urls_for_node(node_id) + + Enum.map(url_attributes, fn attributes -> + {:ok, url} = + attributes + |> Map.put(:node_id, node_id) + |> create_url() + + url + end) + end) + + created_urls + end + @doc """ Creates a url. @@ -97,6 +120,23 @@ defmodule Radiator.Resources do Repo.delete(url) end + @doc """ + Deletes all urls for a given node. + Returns the number of deleted urls. + ## Examples + + iex> delete_urls_for_node(node_id) + 42 + + """ + def delete_urls_for_node(%Node{uuid: node_id}), do: delete_urls_for_node(node_id) + + def delete_urls_for_node(node_id) when is_binary(node_id) do + query = from u in Url, where: u.node_id == ^node_id + {number_of_nodes, nil} = Repo.delete_all(query) + number_of_nodes + end + @doc """ Returns an `%Ecto.Changeset{}` for tracking url changes. diff --git a/lib/radiator/resources/url_worker.ex b/lib/radiator/resources/url_worker.ex index 99783328..6cb2f26d 100644 --- a/lib/radiator/resources/url_worker.ex +++ b/lib/radiator/resources/url_worker.ex @@ -15,7 +15,7 @@ defmodule Radiator.Resources.UrlWorker do def perform(node_id, content) do result = UrlExtractor.extract_urls(content) - # TODO - handle old existing urls for this node, error handling! + Enum.each(result, fn info -> {:ok, _url} = info diff --git a/test/radiator/resources/url_worker_test.exs b/test/radiator/resources/url_worker_test.exs new file mode 100644 index 00000000..0cd9b22d --- /dev/null +++ b/test/radiator/resources/url_worker_test.exs @@ -0,0 +1,32 @@ +defmodule Radiator.Resources.UrlWorkerTest do + use Radiator.DataCase + + alias Radiator.Outline.Node + alias Radiator.OutlineFixtures + alias Radiator.Resources.UrlWorker + + describe "extract_url_positions/1" do + test "extracts urls in text" do + node_id = OutlineFixtures.node_fixture().uuid + + content = """ + bad: https://www.theatlantic.com/politics/archive/2024/10/donald-trump-elon-musk-butler/680174/ + racism: https://www.newyorker.com/magazine/2024/10/14/trumps-dangerous-immigration-obsession + what else? + """ + + UrlWorker.perform(node_id, content) + + updated_node = + Node + |> Repo.get(node_id) + |> Repo.preload(:urls) + + first_url = Enum.find(updated_node.urls, fn url -> url.start_bytes == 7 end) + second_url = Enum.find(updated_node.urls, fn url -> url.start_bytes == 108 end) + assert 2 == Enum.count(updated_node.urls) + assert first_url.url == "https://www.theatlantic.com/politics/archive/2024/10/donald-trump-elon-musk-butler/680174/" + assert second_url.url == "https://www.newyorker.com/magazine/2024/10/14/trumps-dangerous-immigration-obsession" + end + end +end diff --git a/test/radiator/resources_test.exs b/test/radiator/resources_test.exs index 140c999e..7c7167d6 100644 --- a/test/radiator/resources_test.exs +++ b/test/radiator/resources_test.exs @@ -8,33 +8,30 @@ defmodule Radiator.ResourcesbTest do alias Radiator.Resources alias Radiator.Resources.Url - describe "urls" do - setup do - node = - OutlineFixtures.node_fixture() - |> Repo.preload([:episode]) - - episode = node.episode - - %{ - episode: episode, - node: node - } - end + @invalid_attrs %{url: nil, start_bytes: nil, size_bytes: nil} - @invalid_attrs %{url: nil, start_bytes: nil, size_bytes: nil} + describe "list_urls_of_episode/0" do + setup :set_up_single_url - test "list_urls/0 returns all urls", %{episode: episode, node: node} do + test "returns all urls of an episode", %{episode: episode, node: node} do url = url_fixture(node_id: node.uuid) - assert Resources.list_urls(episode.id) == [url] + assert Resources.list_urls_of_episode(episode.id) == [url] end + end + + describe "get_url!/1" do + setup :set_up_single_url test "get_url!/1 returns the url with given id" do url = url_fixture() assert Resources.get_url!(url.id) == url end + end - test "create_url/1 with valid data creates a url", %{node: node} do + describe "create_url!/1" do + setup :set_up_single_url + + test "creates a url with valid data", %{node: node} do valid_attrs = %{url: "some url", start_bytes: 42, size_bytes: 42, node_id: node.uuid} assert {:ok, %Url{} = url} = Resources.create_url(valid_attrs) @@ -43,11 +40,32 @@ defmodule Radiator.ResourcesbTest do assert url.size_bytes == 42 end - test "create_url/1 with invalid data returns error changeset" do + test "with invalid data returns error changeset" do assert {:error, %Ecto.Changeset{}} = Resources.create_url(@invalid_attrs) end + end + + describe "rebuild_node_urls/2" do + setup :set_up_single_url + + test "rebuilds all urls for a node" do + url_text = "https://hexdocs.pm" + node = OutlineFixtures.node_fixture() + old_url = url_fixture(node_id: node.uuid) + + assert [%Url{url: ^url_text, start_bytes: 42, size_bytes: 42}] = + Resources.rebuild_node_urls(node.uuid, [ + %{url: url_text, start_bytes: 42, size_bytes: 42} + ]) + + assert_raise Ecto.NoResultsError, fn -> Resources.get_url!(old_url.id) end + end + end + + describe "update_url/2" do + setup :set_up_single_url - test "update_url/2 with valid data updates the url" do + test "with valid data updates the url" do url = url_fixture() update_attrs = %{url: "some updated url", start_bytes: 43, size_bytes: 43} @@ -57,21 +75,45 @@ defmodule Radiator.ResourcesbTest do assert url.size_bytes == 43 end - test "update_url/2 with invalid data returns error changeset" do + test "with invalid data returns error changeset" do url = url_fixture() assert {:error, %Ecto.Changeset{}} = Resources.update_url(url, @invalid_attrs) assert url == Resources.get_url!(url.id) end + end - test "delete_url/1 deletes the url" do + describe "delete_url/1" do + test " deletes the url" do url = url_fixture() assert {:ok, %Url{}} = Resources.delete_url(url) assert_raise Ecto.NoResultsError, fn -> Resources.get_url!(url.id) end end + end + + describe "delete_urls_for_node/1" do + test "deletes all urls from a node" do + node = OutlineFixtures.node_fixture() + url = url_fixture(node_id: node.uuid) + + assert 1 = Resources.delete_urls_for_node(node) + assert_raise Ecto.NoResultsError, fn -> Resources.get_url!(url.id) end + end + end - test "change_url/1 returns a url changeset" do + describe "change_url/1" do + test "returns a url changeset" do url = url_fixture() assert %Ecto.Changeset{} = Resources.change_url(url) end end + + def set_up_single_url(_) do + node = + OutlineFixtures.node_fixture() + |> Repo.preload([:episode]) + + episode = node.episode + + {:ok, episode: episode, node: node} + end end diff --git a/test/support/fixtures/resources_fixtures.ex b/test/support/fixtures/resources_fixtures.ex index 46321fb1..a5d3c068 100644 --- a/test/support/fixtures/resources_fixtures.ex +++ b/test/support/fixtures/resources_fixtures.ex @@ -16,8 +16,8 @@ defmodule Radiator.ResourcesFixtures do attrs |> Enum.into(%{ size_bytes: 42, - start_bytes: 42, - url: "some url", + start_bytes: 23, + url: "https://elixirschool.com", node_id: node_id }) |> Resources.create_url()