From 5fc3df25bf2e95a3db777605f1437694862c87d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Wo=CC=88ginger?= Date: Sat, 25 Nov 2023 00:19:18 +0100 Subject: [PATCH] add base resources for podcast context --- lib/radiator/podcast.ex | 293 ++++++++++++++++++ lib/radiator/podcast/episode.ex | 23 ++ lib/radiator/podcast/network.ex | 24 ++ lib/radiator/podcast/show.ex | 23 ++ .../20231124220144_create_networks.exs | 11 + .../20231127182625_create_shows.exs | 14 + .../20231127184621_create_episodes.exs | 14 + test/radiator/podcast_test.exs | 166 ++++++++++ test/support/fixtures/podcast_fixtures.ex | 56 ++++ 9 files changed, 624 insertions(+) create mode 100644 lib/radiator/podcast.ex create mode 100644 lib/radiator/podcast/episode.ex create mode 100644 lib/radiator/podcast/network.ex create mode 100644 lib/radiator/podcast/show.ex create mode 100644 priv/repo/migrations/20231124220144_create_networks.exs create mode 100644 priv/repo/migrations/20231127182625_create_shows.exs create mode 100644 priv/repo/migrations/20231127184621_create_episodes.exs create mode 100644 test/radiator/podcast_test.exs create mode 100644 test/support/fixtures/podcast_fixtures.ex diff --git a/lib/radiator/podcast.ex b/lib/radiator/podcast.ex new file mode 100644 index 00000000..b65ba4fc --- /dev/null +++ b/lib/radiator/podcast.ex @@ -0,0 +1,293 @@ +defmodule Radiator.Podcast do + @moduledoc """ + The Podcasts context. + Handles repo operations for networks, shows and episodes. + """ + + import Ecto.Query, warn: false + alias Radiator.Repo + + alias Radiator.Podcast.{Episode, Network, Show} + + @doc """ + Returns the list of networks. + + ## Examples + + iex> list_networks() + [%Network{}, ...] + + """ + def list_networks do + Repo.all(Network) + end + + @doc """ + Gets a single network. + + Raises `Ecto.NoResultsError` if the Network does not exist. + + ## Examples + + iex> get_network!(123) + %Network{} + + iex> get_network!(456) + ** (Ecto.NoResultsError) + + """ + def get_network!(id), do: Repo.get!(Network, id) + + @doc """ + Creates a network. + + ## Examples + + iex> create_network(%{field: value}) + {:ok, %Network{}} + + iex> create_network(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_network(attrs \\ %{}) do + %Network{} + |> Network.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a network. + + ## Examples + + iex> update_network(network, %{field: new_value}) + {:ok, %Network{}} + + iex> update_network(network, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_network(%Network{} = network, attrs) do + network + |> Network.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a network. + + ## Examples + + iex> delete_network(network) + {:ok, %Network{}} + + iex> delete_network(network) + {:error, %Ecto.Changeset{}} + + """ + def delete_network(%Network{} = network) do + Repo.delete(network) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking network changes. + + ## Examples + + iex> change_network(network) + %Ecto.Changeset{data: %Network{}} + + """ + def change_network(%Network{} = network, attrs \\ %{}) do + Network.changeset(network, attrs) + end + + @doc """ + Returns the list of shows. + + ## Examples + + iex> list_shows() + [%Show{}, ...] + + """ + def list_shows do + Repo.all(Show) + end + + @doc """ + Gets a single show. + + Raises `Ecto.NoResultsError` if the Show does not exist. + + ## Examples + + iex> get_show!(123) + %Show{} + + iex> get_show!(456) + ** (Ecto.NoResultsError) + + """ + def get_show!(id), do: Repo.get!(Show, id) + + @doc """ + Creates a show. + + ## Examples + + iex> create_show(%{field: value}) + {:ok, %Show{}} + + iex> create_show(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_show(attrs \\ %{}) do + %Show{} + |> Show.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a show. + + ## Examples + + iex> update_show(show, %{field: new_value}) + {:ok, %Show{}} + + iex> update_show(show, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_show(%Show{} = show, attrs) do + show + |> Show.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a show. + + ## Examples + + iex> delete_show(show) + {:ok, %Show{}} + + iex> delete_show(show) + {:error, %Ecto.Changeset{}} + + """ + def delete_show(%Show{} = show) do + Repo.delete(show) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking show changes. + + ## Examples + + iex> change_show(show) + %Ecto.Changeset{data: %Show{}} + + """ + def change_show(%Show{} = show, attrs \\ %{}) do + Show.changeset(show, attrs) + end + + @doc """ + Returns the list of episodes. + + ## Examples + + iex> list_episodes() + [%Episode{}, ...] + + """ + def list_episodes do + Repo.all(Episode) + end + + @doc """ + Gets a single episode. + + Raises `Ecto.NoResultsError` if the Episode does not exist. + + ## Examples + + iex> get_episode!(123) + %Episode{} + + iex> get_episode!(456) + ** (Ecto.NoResultsError) + + """ + def get_episode!(id), do: Repo.get!(Episode, id) + + @doc """ + Creates a episode. + + ## Examples + + iex> create_episode(%{field: value}) + {:ok, %Episode{}} + + iex> create_episode(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_episode(attrs \\ %{}) do + %Episode{} + |> Episode.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a episode. + + ## Examples + + iex> update_episode(episode, %{field: new_value}) + {:ok, %Episode{}} + + iex> update_episode(episode, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_episode(%Episode{} = episode, attrs) do + episode + |> Episode.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a episode. + + ## Examples + + iex> delete_episode(episode) + {:ok, %Episode{}} + + iex> delete_episode(episode) + {:error, %Ecto.Changeset{}} + + """ + def delete_episode(%Episode{} = episode) do + Repo.delete(episode) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking episode changes. + + ## Examples + + iex> change_episode(episode) + %Ecto.Changeset{data: %Episode{}} + + """ + def change_episode(%Episode{} = episode, attrs \\ %{}) do + Episode.changeset(episode, attrs) + end +end diff --git a/lib/radiator/podcast/episode.ex b/lib/radiator/podcast/episode.ex new file mode 100644 index 00000000..ca4c540a --- /dev/null +++ b/lib/radiator/podcast/episode.ex @@ -0,0 +1,23 @@ +defmodule Radiator.Podcast.Episode do + @moduledoc """ + Represents the Episode model. + TODO: Episodes should be numbered and ordered inside a show. + """ + use Ecto.Schema + import Ecto.Changeset + + alias Radiator.Podcast.Show + + schema "episodes" do + field :title, :string + belongs_to :show, Show + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(episode, attrs) do + episode + |> cast(attrs, [:title, :show_id]) + |> validate_required([:title, :show_id]) + end +end diff --git a/lib/radiator/podcast/network.ex b/lib/radiator/podcast/network.ex new file mode 100644 index 00000000..a38bbde2 --- /dev/null +++ b/lib/radiator/podcast/network.ex @@ -0,0 +1,24 @@ +defmodule Radiator.Podcast.Network do + @moduledoc """ + Represents the network model. + A network can host many shows. + """ + use Ecto.Schema + import Ecto.Changeset + + alias Radiator.Podcast.Show + + schema "networks" do + field :title, :string + + has_many(:shows, Show) + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(network, attrs) do + network + |> cast(attrs, [:title]) + |> validate_required([:title]) + end +end diff --git a/lib/radiator/podcast/show.ex b/lib/radiator/podcast/show.ex new file mode 100644 index 00000000..2e28e4d6 --- /dev/null +++ b/lib/radiator/podcast/show.ex @@ -0,0 +1,23 @@ +defmodule Radiator.Podcast.Show do + @moduledoc """ + Represents the show model. + A show can have many episodes. + """ + use Ecto.Schema + import Ecto.Changeset + alias Radiator.Podcast.{Episode, Network} + + schema "shows" do + field :title, :string + belongs_to :network, Network + has_many(:episodes, Episode) + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(show, attrs) do + show + |> cast(attrs, [:title, :network_id]) + |> validate_required([:title]) + end +end diff --git a/priv/repo/migrations/20231124220144_create_networks.exs b/priv/repo/migrations/20231124220144_create_networks.exs new file mode 100644 index 00000000..2e461317 --- /dev/null +++ b/priv/repo/migrations/20231124220144_create_networks.exs @@ -0,0 +1,11 @@ +defmodule Radiator.Repo.Migrations.CreateNetworks do + use Ecto.Migration + + def change do + create table(:networks) do + add :title, :string + + timestamps(type: :utc_datetime) + end + end +end diff --git a/priv/repo/migrations/20231127182625_create_shows.exs b/priv/repo/migrations/20231127182625_create_shows.exs new file mode 100644 index 00000000..16411e33 --- /dev/null +++ b/priv/repo/migrations/20231127182625_create_shows.exs @@ -0,0 +1,14 @@ +defmodule Radiator.Repo.Migrations.CreateShows do + use Ecto.Migration + + def change do + create table(:shows) do + add :title, :string + add :network_id, references(:networks, on_delete: :nothing) + + timestamps(type: :utc_datetime) + end + + create index(:shows, [:network_id]) + end +end diff --git a/priv/repo/migrations/20231127184621_create_episodes.exs b/priv/repo/migrations/20231127184621_create_episodes.exs new file mode 100644 index 00000000..a60d708e --- /dev/null +++ b/priv/repo/migrations/20231127184621_create_episodes.exs @@ -0,0 +1,14 @@ +defmodule Radiator.Repo.Migrations.CreateEpisodes do + use Ecto.Migration + + def change do + create table(:episodes) do + add :title, :string + add :show_id, references(:shows, on_delete: :nothing) + + timestamps(type: :utc_datetime) + end + + create index(:episodes, [:show_id]) + end +end diff --git a/test/radiator/podcast_test.exs b/test/radiator/podcast_test.exs new file mode 100644 index 00000000..57378371 --- /dev/null +++ b/test/radiator/podcast_test.exs @@ -0,0 +1,166 @@ +defmodule Radiator.PodcastTest do + use Radiator.DataCase + + import Radiator.PodcastFixtures + + alias Radiator.Podcast + alias Radiator.Podcast.{Episode, Network, Show} + + describe "networks" do + @invalid_attrs %{title: nil} + + test "list_networks/0 returns all networks" do + network = network_fixture() + assert Podcast.list_networks() == [network] + end + + test "get_network!/1 returns the network with given id" do + network = network_fixture() + assert Podcast.get_network!(network.id) == network + end + + test "create_network/1 with valid data creates a network" do + valid_attrs = %{title: "some title"} + + assert {:ok, %Network{} = network} = Podcast.create_network(valid_attrs) + assert network.title == "some title" + end + + test "create_network/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Podcast.create_network(@invalid_attrs) + end + + test "update_network/2 with valid data updates the network" do + network = network_fixture() + update_attrs = %{title: "some updated title"} + + assert {:ok, %Network{} = network} = Podcast.update_network(network, update_attrs) + assert network.title == "some updated title" + end + + test "update_network/2 with invalid data returns error changeset" do + network = network_fixture() + assert {:error, %Ecto.Changeset{}} = Podcast.update_network(network, @invalid_attrs) + assert network == Podcast.get_network!(network.id) + end + + test "delete_network/1 deletes the network" do + network = network_fixture() + assert {:ok, %Network{}} = Podcast.delete_network(network) + assert_raise Ecto.NoResultsError, fn -> Podcast.get_network!(network.id) end + end + + test "change_network/1 returns a network changeset" do + network = network_fixture() + assert %Ecto.Changeset{} = Podcast.change_network(network) + end + end + + describe "shows" do + @invalid_attrs %{title: nil, hostname: nil} + + test "list_shows/0 returns all shows" do + show = show_fixture() + assert Podcast.list_shows() == [show] + end + + test "get_show!/1 returns the show with given id" do + show = show_fixture() + assert Podcast.get_show!(show.id) == show + end + + test "create_show/1 with valid data creates a show" do + network = network_fixture() + valid_attrs = %{title: "some title", network_id: network.id} + + assert {:ok, %Show{} = show} = Podcast.create_show(valid_attrs) + assert show.title == "some title" + assert show.network_id == network.id + end + + test "create_show/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Podcast.create_show(@invalid_attrs) + end + + test "update_show/2 with valid data updates the show" do + show = show_fixture() + updated_network = network_fixture() + update_attrs = %{title: "some updated title", network_id: updated_network.id} + + assert {:ok, %Show{} = show} = Podcast.update_show(show, update_attrs) + assert show.title == "some updated title" + assert show.network_id == updated_network.id + end + + test "update_show/2 with invalid data returns error changeset" do + show = show_fixture() + assert {:error, %Ecto.Changeset{}} = Podcast.update_show(show, @invalid_attrs) + assert show == Podcast.get_show!(show.id) + end + + test "delete_show/1 deletes the show" do + show = show_fixture() + assert {:ok, %Show{}} = Podcast.delete_show(show) + assert_raise Ecto.NoResultsError, fn -> Podcast.get_show!(show.id) end + end + + test "change_show/1 returns a show changeset" do + show = show_fixture() + assert %Ecto.Changeset{} = Podcast.change_show(show) + end + end + + describe "episodes" do + @invalid_attrs %{title: nil} + + test "list_episodes/0 returns all episodes" do + episode = episode_fixture() + assert Podcast.list_episodes() == [episode] + end + + test "get_episode!/1 returns the episode with given id" do + episode = episode_fixture() + assert Podcast.get_episode!(episode.id) == episode + end + + test "create_episode/1 with valid data creates a episode" do + show = show_fixture() + valid_attrs = %{title: "some title", show_id: show.id} + + assert {:ok, %Episode{} = episode} = Podcast.create_episode(valid_attrs) + assert episode.title == "some title" + assert episode.show_id == show.id + end + + test "create_episode/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Podcast.create_episode(@invalid_attrs) + end + + test "update_episode/2 with valid data updates the episode" do + episode = episode_fixture() + updated_podcast = show_fixture() + update_attrs = %{title: "some updated title", show_id: updated_podcast.id} + + assert {:ok, %Episode{} = episode} = Podcast.update_episode(episode, update_attrs) + assert episode.title == "some updated title" + assert episode.show_id == updated_podcast.id + end + + test "update_episode/2 with invalid data returns error changeset" do + episode = episode_fixture() + assert {:error, %Ecto.Changeset{}} = Podcast.update_episode(episode, @invalid_attrs) + assert episode == Podcast.get_episode!(episode.id) + end + + 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 + end + + test "change_episode/1 returns a episode changeset" do + episode = episode_fixture() + assert %Ecto.Changeset{} = Podcast.change_episode(episode) + end + end +end diff --git a/test/support/fixtures/podcast_fixtures.ex b/test/support/fixtures/podcast_fixtures.ex new file mode 100644 index 00000000..85ab8f0b --- /dev/null +++ b/test/support/fixtures/podcast_fixtures.ex @@ -0,0 +1,56 @@ +defmodule Radiator.PodcastFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Radiator.Podcast` context. + """ + alias Radiator.Podcast + + @doc """ + Generate a network. + """ + def network_fixture(attrs \\ %{}) do + {:ok, network} = + attrs + |> Enum.into(%{ + title: "metanetwork" + }) + |> Podcast.create_network() + + network + end + + @doc """ + Generate a show. + """ + def show_fixture(attrs \\ %{}) do + network = network_fixture() + + {:ok, show} = + attrs + |> Enum.into(%{ + hostname: "some hostname", + title: "some title", + network: network + }) + |> Podcast.create_show() + + show + end + + @doc """ + Generate a episode. + """ + def episode_fixture(attrs \\ %{}) do + show = show_fixture() + + {:ok, episode} = + attrs + |> Enum.into(%{ + title: "my show episode 23", + show_id: show.id + }) + |> Podcast.create_episode() + + episode + end +end