Skip to content

Commit

Permalink
feature: Soft deleting episode
Browse files Browse the repository at this point in the history
Marking episodes as `is_deleted = true` and setting `deleted_at = now`.
`list_available_episodes` gives all episodes that are NOT marked as deleted.
`list_all_episodes` gives all episodes including the once that are marked as deleted.
  • Loading branch information
mwingert committed Oct 7, 2024
1 parent 0c43cd2 commit bb272c7
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 14 deletions.
63 changes: 56 additions & 7 deletions lib/radiator/podcast.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 """
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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 """
Expand Down
20 changes: 19 additions & 1 deletion lib/radiator/podcast/episode.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
43 changes: 40 additions & 3 deletions lib/radiator_web/live/episode_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions priv/repo/migrations/20240918192918_add_is_deleted_to_episodes.exs
Original file line number Diff line number Diff line change
@@ -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
28 changes: 25 additions & 3 deletions test/radiator/podcast_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit bb272c7

Please sign in to comment.