diff --git a/lib/radiator/podcast.ex b/lib/radiator/podcast.ex index c121569b..6e148fc4 100644 --- a/lib/radiator/podcast.ex +++ b/lib/radiator/podcast.ex @@ -285,16 +285,60 @@ defmodule Radiator.Podcast do end @doc """ - Returns the list of episodes. + Returns the query for list of episodes exluding the once that are marked as deleted. ## Examples - iex> list_episodes() + iex> list_available_episodes_query() + %Ecto.Query{} + + """ + def list_available_episodes_query do + from e in Episode, + where: [is_deleted: false], + order_by: [desc: e.number] + end + + @doc """ + Returns the query for list of episodes including the (soft) deleted once. + + ## Examples + + iex> list_all_episodes_query() + %Ecto.Query{} + + """ + def list_all_episodes_query do + from e in Episode, + order_by: [desc: e.number] + end + + @doc """ + Returns the list of episodes exluding the once that are marked as deleted. + + ## Examples + + iex> list_available_episodes() [%Episode{}, ...] """ - def list_episodes do - Repo.all(Episode) + def list_available_episodes do + list_available_episodes_query() + |> Repo.all() + end + + @doc """ + Returns the list of episodes including the (soft) deleted once. + + ## Examples + + iex> list_all_episodes() + [%Episode{}, ...] + + """ + def list_all_episodes do + list_all_episodes_query() + |> Repo.all() end @doc """ @@ -330,7 +374,10 @@ defmodule Radiator.Podcast do def get_current_episode_for_show(show_id) do Repo.one( - from e in Episode, where: e.show_id == ^show_id, order_by: [desc: e.number], limit: 1 + from e in Episode, + where: [show_id: ^show_id, is_deleted: false], + order_by: [desc: e.number], + limit: 1 ) end @@ -356,7 +403,7 @@ defmodule Radiator.Podcast do query = from e in Episode, select: max(e.number), - where: [show_id: ^show_id] + where: [show_id: ^show_id, is_deleted: false] max_number = Repo.one(query) || 0 max_number + 1 @@ -393,7 +440,9 @@ defmodule Radiator.Podcast do """ def delete_episode(%Episode{} = episode) do - Repo.delete(episode) + episode + |> Episode.changeset(%{is_deleted: true}) + |> Repo.update() end @doc """ diff --git a/lib/radiator/podcast/episode.ex b/lib/radiator/podcast/episode.ex index bbaa54d1..845d6c58 100644 --- a/lib/radiator/podcast/episode.ex +++ b/lib/radiator/podcast/episode.ex @@ -14,6 +14,8 @@ defmodule Radiator.Podcast.Episode do field :number, :integer field :publish_date, :date field :slug, :string + field :is_deleted, :boolean, default: false + field :deleted_at, :utc_datetime belongs_to :show, Show @@ -23,10 +25,11 @@ defmodule Radiator.Podcast.Episode do @doc false def changeset(episode, attrs) do episode - |> cast(attrs, [:title, :show_id, :number, :publish_date, :slug]) + |> cast(attrs, [:title, :show_id, :number, :publish_date, :slug, :is_deleted, :deleted_at]) |> validate_required([:title, :show_id, :number]) |> validate_length(:title, min: 3) |> maybe_update_slug() + |> validate_deleted_at() end defp maybe_update_slug(changeset) do @@ -43,4 +46,19 @@ defmodule Radiator.Podcast.Episode do |> put_change(:slug, new_slug) end end + + defp validate_deleted_at(changeset) do + # Check if the is_deleted has changed + case get_change(changeset, :is_deleted) do + true -> + now = DateTime.utc_now() |> DateTime.truncate(:second) + put_change(changeset, :deleted_at, now) + + false -> + put_change(changeset, :deleted_at, nil) + + nil -> + changeset + end + end end diff --git a/lib/radiator_web/live/episode_live/index.ex b/lib/radiator_web/live/episode_live/index.ex index cc5be809..113ec1d2 100644 --- a/lib/radiator_web/live/episode_live/index.ex +++ b/lib/radiator_web/live/episode_live/index.ex @@ -17,7 +17,9 @@ defmodule RadiatorWeb.EpisodeLive.Index do @impl true def mount(%{"show" => show_id} = params, _session, socket) do - show = Podcast.get_show!(show_id, preload: :episodes) + show = + Podcast.get_show!(show_id, preload: [episodes: Podcast.list_available_episodes_query()]) + episode = get_selected_episode(params) socket @@ -62,6 +64,10 @@ defmodule RadiatorWeb.EpisodeLive.Index do save_episode(socket, socket.assigns.live_action, params) end + def handle_event("delete", params, socket) do + delete_episode(socket, params) + end + @impl true def handle_info(%NodeInsertedEvent{} = event, socket), do: proxy_event(event, socket) def handle_info(%NodeContentChangedEvent{} = event, socket), do: proxy_event(event, socket) @@ -120,7 +126,10 @@ defmodule RadiatorWeb.EpisodeLive.Index do "episode_id" => episode.id }) - show = Podcast.reload_assoc(socket.assigns.show, [:episodes]) + show = + Podcast.reload_assoc(socket.assigns.show, + episodes: Podcast.list_available_episodes_query() + ) socket |> assign(:show, show) @@ -137,7 +146,10 @@ defmodule RadiatorWeb.EpisodeLive.Index do defp save_episode(socket, :edit, params) do case Podcast.update_episode(socket.assigns.episode, params) do {:ok, episode} -> - show = Podcast.reload_assoc(socket.assigns.show, [:episodes]) + show = + Podcast.reload_assoc(socket.assigns.show, + episodes: Podcast.list_available_episodes_query() + ) socket |> assign(:show, show) @@ -151,6 +163,31 @@ defmodule RadiatorWeb.EpisodeLive.Index do end end + defp delete_episode(socket, %{"id" => id}) do + id + |> Podcast.get_episode!() + |> Podcast.delete_episode() + |> case do + {:ok, _episode} -> + show = + Podcast.reload_assoc(socket.assigns.show, + episodes: Podcast.list_available_episodes_query() + ) + + socket + |> assign(:show, show) + |> assign(:episodes, show.episodes) + |> put_flash(:info, "Episode deleted") + |> push_patch(to: ~p"/admin/podcast/#{socket.assigns.show}") + |> reply(:noreply) + + {:error, _changeset} -> + socket + |> put_flash(:error, "Failed to delete episode") + |> reply(:noreply) + end + end + defp get_selected_episode(%{"episode" => episode_id}) do Podcast.get_episode!(episode_id) end diff --git a/priv/repo/migrations/20240918192918_add_is_deleted_to_episodes.exs b/priv/repo/migrations/20240918192918_add_is_deleted_to_episodes.exs new file mode 100644 index 00000000..44ffcbc6 --- /dev/null +++ b/priv/repo/migrations/20240918192918_add_is_deleted_to_episodes.exs @@ -0,0 +1,10 @@ +defmodule Radiator.Repo.Migrations.AddIsDeletedToEpisodes do + use Ecto.Migration + + def change do + alter table(:episodes) do + add :is_deleted, :boolean, default: false + add :deleted_at, :utc_datetime + end + end +end diff --git a/test/radiator/podcast_test.exs b/test/radiator/podcast_test.exs index fa1671c4..92f8b28a 100644 --- a/test/radiator/podcast_test.exs +++ b/test/radiator/podcast_test.exs @@ -156,9 +156,27 @@ defmodule Radiator.PodcastTest do describe "episodes" do @invalid_attrs %{title: nil} - test "list_episodes/0 returns all episodes" do + test "list_all_episodes/0 returns all episodes" do + deleted_episode = + episode_fixture( + is_deleted: true, + deleted_at: DateTime.utc_now() |> DateTime.truncate(:second) + ) + + assert Podcast.list_all_episodes() == [deleted_episode] + end + + test "list_available_episodes/0 returns all episodes" do episode = episode_fixture() - assert Podcast.list_episodes() == [episode] + + _deleted_episode = + episode_fixture( + is_deleted: true, + deleted_at: DateTime.utc_now() |> DateTime.truncate(:second) + ) + + found_episodes = Podcast.list_available_episodes() + assert found_episodes == [episode] end test "get_episode!/1 returns the episode with given id" do @@ -201,7 +219,11 @@ defmodule Radiator.PodcastTest do test "delete_episode/1 deletes the episode" do episode = episode_fixture() assert {:ok, %Episode{}} = Podcast.delete_episode(episode) - assert_raise Ecto.NoResultsError, fn -> Podcast.get_episode!(episode.id) end + episode = Podcast.get_episode!(episode.id) + assert episode.is_deleted == true + assert episode.deleted_at != nil + + assert Podcast.list_available_episodes() == [] end test "change_episode/1 returns a episode changeset" do