From bdef6c75bbaa46c85fcc72e3129772cc03be62d9 Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 2 Feb 2024 10:45:21 -0800 Subject: [PATCH] Change Channel to Source (#12) * Ensure channel detail lookup doesn't download the video - oops * Ran the needful migrations for replacing channels with sources * got media source test back working * channel tasks test * Media indexing worker test * media tests * Got all tests working except controller tests * got all tests working * Renamed Channel struct to Source * renamed ChannelTasks * More renaming; renamed channel details module * Removed Channel yt-dlp module, instead throwing things in the VideoCollection module * Renamed what looks like the last of the outstanding data --- .iex.exs | 4 +- lib/pinchflat/media.ex | 8 +- lib/pinchflat/media/media_item.ex | 10 +- .../media_client/backends/yt_dlp/channel.ex | 32 --- .../backends/yt_dlp/video_collection.ex | 59 +++-- lib/pinchflat/media_client/channel_details.ex | 41 ---- lib/pinchflat/media_client/source_details.ex | 41 ++++ .../media_client/video_downloader.ex | 4 +- lib/pinchflat/media_source.ex | 106 ++++----- .../media_source/{channel.ex => source.ex} | 21 +- lib/pinchflat/profiles/media_profile.ex | 4 +- lib/pinchflat/tasks.ex | 10 +- .../{channel_tasks.ex => source_tasks.ex} | 18 +- lib/pinchflat/tasks/task.ex | 6 +- .../workers/media_indexing_worker.ex | 30 +-- .../media_sources/channel_controller.ex | 75 ------- .../media_sources/channel_html/edit.html.heex | 12 - .../channel_html/index.html.heex | 28 --- .../media_sources/channel_html/new.html.heex | 12 - .../media_sources/channel_html/show.html.heex | 17 -- .../media_sources/source_controller.ex | 75 +++++++ .../{channel_html.ex => source_html.ex} | 8 +- .../media_sources/source_html/edit.html.heex | 12 + .../media_sources/source_html/index.html.heex | 24 ++ .../media_sources/source_html/new.html.heex | 12 + .../media_sources/source_html/show.html.heex | 17 ++ .../source_form.html.heex} | 5 +- lib/pinchflat_web/router.ex | 2 +- ...0724_rename_channel_and_related_fields.exs | 45 ++++ .../20240202043042_drop_channels_table.exs | 21 ++ .../backends/yt_dlp/channel_test.exs | 45 ---- .../backends/yt_dlp/video_collection_test.exs | 49 ++++- ...tails_test.exs => source_details_test.exs} | 22 +- .../media_client/video_downloader_test.exs | 2 +- test/pinchflat/media_source_test.exs | 208 +++++++++--------- test/pinchflat/media_test.exs | 18 +- test/pinchflat/tasks/channel_tasks_test.exs | 45 ---- test/pinchflat/tasks/source_tasks_test.exs | 45 ++++ test/pinchflat/tasks_test.exs | 60 ++--- .../workers/media_indexing_worker_test.exs | 52 ++--- .../workers/video_download_worker_test.exs | 2 +- .../controllers/channel_controller_test.exs | 119 ---------- .../controllers/source_controller_test.exs | 120 ++++++++++ test/support/fixtures/media_fixtures.ex | 2 +- .../support/fixtures/media_source_fixtures.ex | 19 +- test/support/fixtures/tasks_fixtures.ex | 2 +- 46 files changed, 806 insertions(+), 763 deletions(-) delete mode 100644 lib/pinchflat/media_client/backends/yt_dlp/channel.ex delete mode 100644 lib/pinchflat/media_client/channel_details.ex create mode 100644 lib/pinchflat/media_client/source_details.ex rename lib/pinchflat/media_source/{channel.ex => source.ex} (51%) rename lib/pinchflat/tasks/{channel_tasks.ex => source_tasks.ex} (65%) delete mode 100644 lib/pinchflat_web/controllers/media_sources/channel_controller.ex delete mode 100644 lib/pinchflat_web/controllers/media_sources/channel_html/edit.html.heex delete mode 100644 lib/pinchflat_web/controllers/media_sources/channel_html/index.html.heex delete mode 100644 lib/pinchflat_web/controllers/media_sources/channel_html/new.html.heex delete mode 100644 lib/pinchflat_web/controllers/media_sources/channel_html/show.html.heex create mode 100644 lib/pinchflat_web/controllers/media_sources/source_controller.ex rename lib/pinchflat_web/controllers/media_sources/{channel_html.ex => source_html.ex} (77%) create mode 100644 lib/pinchflat_web/controllers/media_sources/source_html/edit.html.heex create mode 100644 lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex create mode 100644 lib/pinchflat_web/controllers/media_sources/source_html/new.html.heex create mode 100644 lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex rename lib/pinchflat_web/controllers/media_sources/{channel_html/channel_form.html.heex => source_html/source_form.html.heex} (73%) create mode 100644 priv/repo/migrations/20240202040724_rename_channel_and_related_fields.exs create mode 100644 priv/repo/migrations/20240202043042_drop_channels_table.exs delete mode 100644 test/pinchflat/media_client/backends/yt_dlp/channel_test.exs rename test/pinchflat/media_client/{channel_details_test.exs => source_details_test.exs} (63%) delete mode 100644 test/pinchflat/tasks/channel_tasks_test.exs create mode 100644 test/pinchflat/tasks/source_tasks_test.exs delete mode 100644 test/pinchflat_web/controllers/channel_controller_test.exs create mode 100644 test/pinchflat_web/controllers/source_controller_test.exs diff --git a/.iex.exs b/.iex.exs index bc268647..d23dd0cb 100644 --- a/.iex.exs +++ b/.iex.exs @@ -3,7 +3,7 @@ alias Pinchflat.Repo alias Pinchflat.Tasks.Task alias Pinchflat.Media.MediaItem alias Pinchflat.Media.MediaMetadata -alias Pinchflat.MediaSource.Channel +alias Pinchflat.MediaSource.Source alias Pinchflat.Profiles.MediaProfile alias Pinchflat.Tasks @@ -11,4 +11,4 @@ alias Pinchflat.Media alias Pinchflat.Profiles alias Pinchflat.MediaSource -alias Pinchflat.MediaClient.{ChannelDetails, VideoDownloader} +alias Pinchflat.MediaClient.{SourceDetails, VideoDownloader} diff --git a/lib/pinchflat/media.ex b/lib/pinchflat/media.ex index c9b0371b..fe864ae5 100644 --- a/lib/pinchflat/media.ex +++ b/lib/pinchflat/media.ex @@ -8,7 +8,7 @@ defmodule Pinchflat.Media do alias Pinchflat.Repo alias Pinchflat.Tasks alias Pinchflat.Media.MediaItem - alias Pinchflat.MediaSource.Channel + alias Pinchflat.MediaSource.Source @doc """ Returns the list of media_items. Returns [%MediaItem{}, ...]. @@ -18,15 +18,15 @@ defmodule Pinchflat.Media do end @doc """ - Returns a list of pending media_items for a given channel, where + Returns a list of pending media_items for a given source, where pending means the `media_filepath` is `nil`. Returns [%MediaItem{}, ...]. """ - def list_pending_media_items_for(%Channel{} = channel) do + def list_pending_media_items_for(%Source{} = source) do from( m in MediaItem, - where: m.channel_id == ^channel.id and is_nil(m.media_filepath) + where: m.source_id == ^source.id and is_nil(m.media_filepath) ) |> Repo.all() end diff --git a/lib/pinchflat/media/media_item.ex b/lib/pinchflat/media/media_item.ex index 9db15d30..430c3e56 100644 --- a/lib/pinchflat/media/media_item.ex +++ b/lib/pinchflat/media/media_item.ex @@ -7,11 +7,11 @@ defmodule Pinchflat.Media.MediaItem do import Ecto.Changeset alias Pinchflat.Tasks.Task - alias Pinchflat.MediaSource.Channel + alias Pinchflat.MediaSource.Source alias Pinchflat.Media.MediaMetadata - @required_fields ~w(media_id channel_id)a - @allowed_fields ~w(title media_id media_filepath channel_id subtitle_filepaths)a + @required_fields ~w(media_id source_id)a + @allowed_fields ~w(title media_id media_filepath source_id subtitle_filepaths)a schema "media_items" do field :title, :string @@ -22,7 +22,7 @@ defmodule Pinchflat.Media.MediaItem do # Will very likely revisit because I can't leave well-enough alone. field :subtitle_filepaths, {:array, {:array, :string}}, default: [] - belongs_to :channel, Channel + belongs_to :source, Source has_one :metadata, MediaMetadata, on_replace: :update @@ -37,6 +37,6 @@ defmodule Pinchflat.Media.MediaItem do |> cast(attrs, @allowed_fields) |> cast_assoc(:metadata, with: &MediaMetadata.changeset/2, required: false) |> validate_required(@required_fields) - |> unique_constraint([:media_id, :channel_id]) + |> unique_constraint([:media_id, :source_id]) end end diff --git a/lib/pinchflat/media_client/backends/yt_dlp/channel.ex b/lib/pinchflat/media_client/backends/yt_dlp/channel.ex deleted file mode 100644 index d97f88d2..00000000 --- a/lib/pinchflat/media_client/backends/yt_dlp/channel.ex +++ /dev/null @@ -1,32 +0,0 @@ -defmodule Pinchflat.MediaClient.Backends.YtDlp.Channel do - @moduledoc """ - Contains utilities for working with a channel's videos - """ - - use Pinchflat.MediaClient.Backends.YtDlp.VideoCollection - alias Pinchflat.MediaClient.ChannelDetails - - @doc """ - Gets a channel's ID and name from its URL. - - yt-dlp does not _really_ have channel-specific functions, so - instead we're fetching just the first video (using playlist_end: 1) - and parsing the channel ID and name from _its_ metadata - - Returns {:ok, %ChannelDetails{}} | {:error, any, ...}. - """ - def get_channel_details(channel_url) do - opts = [playlist_end: 1] - - with {:ok, output} <- backend_runner().run(channel_url, opts, "%(.{channel,channel_id})j"), - {:ok, parsed_json} <- Phoenix.json_library().decode(output) do - {:ok, ChannelDetails.new(parsed_json["channel_id"], parsed_json["channel"])} - else - err -> err - end - end - - defp backend_runner do - Application.get_env(:pinchflat, :yt_dlp_runner) - end -end diff --git a/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex b/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex index d89c0643..624689b3 100644 --- a/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex +++ b/lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex @@ -1,28 +1,47 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollection do @moduledoc """ - Contains utilities for working with collections of videos (ie: channels, playlists). + Contains utilities for working with collections of + videos (aka: a source [ie: channels, playlists]). + """ + + alias Pinchflat.MediaClient.SourceDetails + + @doc """ + Returns a list of strings representing the video ids in the collection. + + Returns {:ok, [binary()]} | {:error, any, ...}. + """ + def get_video_ids(url, command_opts \\ []) do + runner = Application.get_env(:pinchflat, :yt_dlp_runner) + opts = command_opts ++ [:simulate, :skip_download] + + case runner.run(url, opts, "%(id)s") do + {:ok, output} -> {:ok, String.split(output, "\n", trim: true)} + res -> res + end + end + + @doc """ + Gets a source's ID and name from its URL. - Meant to be included in other modules but can be used on its own. Channels and playlists - will have many of their own methods, but also share a lot of methods. This module is for - those shared methods. + yt-dlp does not _really_ have source-specific functions, so + instead we're fetching just the first video (using playlist_end: 1) + and parsing the source ID and name from _its_ metadata + + Returns {:ok, %SourceDetails{}} | {:error, any, ...}. """ + def get_source_details(source_url) do + opts = [:skip_download, playlist_end: 1] - defmacro __using__(_) do - quote do - @doc """ - Returns a list of strings representing the video ids in the collection. - - Returns {:ok, [binary()]} | {:error, any, ...}. - """ - def get_video_ids(url, command_opts \\ []) do - runner = Application.get_env(:pinchflat, :yt_dlp_runner) - opts = command_opts ++ [:simulate, :skip_download] - - case runner.run(url, opts, "%(id)s") do - {:ok, output} -> {:ok, String.split(output, "\n", trim: true)} - res -> res - end - end + with {:ok, output} <- backend_runner().run(source_url, opts, "%(.{channel,channel_id})j"), + {:ok, parsed_json} <- Phoenix.json_library().decode(output) do + {:ok, SourceDetails.new(parsed_json["channel_id"], parsed_json["channel"])} + else + err -> err end end + + defp backend_runner do + Application.get_env(:pinchflat, :yt_dlp_runner) + end end diff --git a/lib/pinchflat/media_client/channel_details.ex b/lib/pinchflat/media_client/channel_details.ex deleted file mode 100644 index 021238b9..00000000 --- a/lib/pinchflat/media_client/channel_details.ex +++ /dev/null @@ -1,41 +0,0 @@ -defmodule Pinchflat.MediaClient.ChannelDetails do - @moduledoc """ - This is the integration layer for actually working with channels. - - Technically hardcodes the yt-dlp backend for now, but should leave - it open-ish for future expansion (just in case). - """ - @enforce_keys [:id, :name] - defstruct [:id, :name] - - alias Pinchflat.MediaClient.Backends.YtDlp.Channel, as: YtDlpChannel - - @doc false - def new(id, name) do - %__MODULE__{id: id, name: name} - end - - @doc """ - Gets a channel's ID and name from its URL, using the given backend. - - Returns {:ok, map()} | {:error, any, ...}. - """ - def get_channel_details(channel_url, backend \\ :yt_dlp) do - channel_module(backend).get_channel_details(channel_url) - end - - @doc """ - Returns a list of video IDs for the given channel URL, using the given backend. - - Returns {:ok, list(binary())} | {:error, any, ...}. - """ - def get_video_ids(channel_url, backend \\ :yt_dlp) do - channel_module(backend).get_video_ids(channel_url) - end - - defp channel_module(backend) do - case backend do - :yt_dlp -> YtDlpChannel - end - end -end diff --git a/lib/pinchflat/media_client/source_details.ex b/lib/pinchflat/media_client/source_details.ex new file mode 100644 index 00000000..75c70779 --- /dev/null +++ b/lib/pinchflat/media_client/source_details.ex @@ -0,0 +1,41 @@ +defmodule Pinchflat.MediaClient.SourceDetails do + @moduledoc """ + This is the integration layer for actually working with sources. + + Technically hardcodes the yt-dlp backend for now, but should leave + it open-ish for future expansion (just in case). + """ + @enforce_keys [:id, :name] + defstruct [:id, :name] + + alias Pinchflat.MediaClient.Backends.YtDlp.VideoCollection, as: YtDlpSource + + @doc false + def new(id, name) do + %__MODULE__{id: id, name: name} + end + + @doc """ + Gets a source's ID and name from its URL, using the given backend. + + Returns {:ok, map()} | {:error, any, ...}. + """ + def get_source_details(source_url, backend \\ :yt_dlp) do + source_module(backend).get_source_details(source_url) + end + + @doc """ + Returns a list of video IDs for the given source URL, using the given backend. + + Returns {:ok, list(binary())} | {:error, any, ...}. + """ + def get_video_ids(source_url, backend \\ :yt_dlp) do + source_module(backend).get_video_ids(source_url) + end + + defp source_module(backend) do + case backend do + :yt_dlp -> YtDlpSource + end + end +end diff --git a/lib/pinchflat/media_client/video_downloader.ex b/lib/pinchflat/media_client/video_downloader.ex index a993f59f..68d7621d 100644 --- a/lib/pinchflat/media_client/video_downloader.ex +++ b/lib/pinchflat/media_client/video_downloader.ex @@ -25,8 +25,8 @@ defmodule Pinchflat.MediaClient.VideoDownloader do Returns {:ok, %MediaItem{}} | {:error, any, ...any} """ def download_for_media_item(%MediaItem{} = media_item, backend \\ :yt_dlp) do - item_with_preloads = Repo.preload(media_item, [:metadata, channel: :media_profile]) - media_profile = item_with_preloads.channel.media_profile + item_with_preloads = Repo.preload(media_item, [:metadata, source: :media_profile]) + media_profile = item_with_preloads.source.media_profile case download_for_media_profile(media_item.media_id, media_profile, backend) do {:ok, parsed_json} -> diff --git a/lib/pinchflat/media_source.ex b/lib/pinchflat/media_source.ex index 8f8b6f73..88dddd75 100644 --- a/lib/pinchflat/media_source.ex +++ b/lib/pinchflat/media_source.ex @@ -8,34 +8,34 @@ defmodule Pinchflat.MediaSource do alias Pinchflat.Tasks alias Pinchflat.Media - alias Pinchflat.Tasks.ChannelTasks - alias Pinchflat.MediaSource.Channel - alias Pinchflat.MediaClient.ChannelDetails + alias Pinchflat.Tasks.SourceTasks + alias Pinchflat.MediaSource.Source + alias Pinchflat.MediaClient.SourceDetails @doc """ - Returns the list of channels. Returns [%Channel{}, ...] + Returns the list of sources. Returns [%Source{}, ...] """ - def list_channels do - Repo.all(Channel) + def list_sources do + Repo.all(Source) end @doc """ - Gets a single channel. + Gets a single source. - Returns %Channel{}. Raises `Ecto.NoResultsError` if the Channel does not exist. + Returns %Source{}. Raises `Ecto.NoResultsError` if the Source does not exist. """ - def get_channel!(id), do: Repo.get!(Channel, id) + def get_source!(id), do: Repo.get!(Source, id) @doc """ - Creates a channel. May attempt to pull additional channel details from the - original_url (if provided). Will attempt to start indexing the channel's + Creates a source. May attempt to pull additional source details from the + original_url (if provided). Will attempt to start indexing the source's media if successfully inserted. - Returns {:ok, %Channel{}} | {:error, %Ecto.Changeset{}} + Returns {:ok, %Source{}} | {:error, %Ecto.Changeset{}} """ - def create_channel(attrs) do - %Channel{} - |> change_channel_from_url(attrs) + def create_source(attrs) do + %Source{} + |> change_source_from_url(attrs) |> commit_and_start_indexing() end @@ -45,12 +45,12 @@ defmodule Pinchflat.MediaSource do Returns [%MediaItem{}, ...] | [%Ecto.Changeset{}, ...] """ - def index_media_items(%Channel{} = channel) do - {:ok, media_ids} = ChannelDetails.get_video_ids(channel.original_url) + def index_media_items(%Source{} = source) do + {:ok, media_ids} = SourceDetails.get_video_ids(source.original_url) media_ids |> Enum.map(fn media_id -> - attrs = %{channel_id: channel.id, media_id: media_id} + attrs = %{source_id: source.id, media_id: media_id} case Media.create_media_item(attrs) do {:ok, media_item} -> media_item @@ -60,67 +60,67 @@ defmodule Pinchflat.MediaSource do end @doc """ - Updates a channel. May attempt to pull additional channel details from the - original_url (if changed). May attempt to start indexing the channel's + Updates a source. May attempt to pull additional source details from the + original_url (if changed). May attempt to start indexing the source's media if the indexing frequency has been changed. Existing indexing tasks will be cancelled if the indexing frequency has been - changed (logic in `ChannelTasks.kickoff_indexing_task`) + changed (logic in `SourceTasks.kickoff_indexing_task`) - Returns {:ok, %Channel{}} | {:error, %Ecto.Changeset{}} + Returns {:ok, %Source{}} | {:error, %Ecto.Changeset{}} """ - def update_channel(%Channel{} = channel, attrs) do - channel - |> change_channel_from_url(attrs) + def update_source(%Source{} = source, attrs) do + source + |> change_source_from_url(attrs) |> commit_and_start_indexing() end @doc """ - Deletes a channel and it's associated tasks (of any state). + Deletes a source and it's associated tasks (of any state). - Returns {:ok, %Channel{}} | {:error, %Ecto.Changeset{}} + Returns {:ok, %Source{}} | {:error, %Ecto.Changeset{}} """ - def delete_channel(%Channel{} = channel) do - Tasks.delete_tasks_for(channel) - Repo.delete(channel) + def delete_source(%Source{} = source) do + Tasks.delete_tasks_for(source) + Repo.delete(source) end @doc """ - Returns an `%Ecto.Changeset{}` for tracking channel changes. + Returns an `%Ecto.Changeset{}` for tracking source changes. """ - def change_channel(%Channel{} = channel, attrs \\ %{}) do - Channel.changeset(channel, attrs) + def change_source(%Source{} = source, attrs \\ %{}) do + Source.changeset(source, attrs) end @doc """ - Returns an `%Ecto.Changeset{}` for tracking channel changes and additionally - fetches channel details from the original_url (if provided). If the channel + Returns an `%Ecto.Changeset{}` for tracking source changes and additionally + fetches source details from the original_url (if provided). If the source details cannot be fetched, an error is added to the changeset. - Note that this fetches channel details as long as the `original_url` is present. + Note that this fetches source details as long as the `original_url` is present. This means that it'll go for it even if a changeset is otherwise invalid. This is pretty easy to change, but for MVP I'm not concerned. """ - def change_channel_from_url(%Channel{} = channel, attrs) do - case change_channel(channel, attrs) do + def change_source_from_url(%Source{} = source, attrs) do + case change_source(source, attrs) do %Ecto.Changeset{changes: %{original_url: _}} = changeset -> - add_channel_details_to_changeset(channel, changeset) + add_source_details_to_changeset(source, changeset) changeset -> changeset end end - defp add_channel_details_to_changeset(channel, changeset) do + defp add_source_details_to_changeset(source, changeset) do %Ecto.Changeset{changes: changes} = changeset - case ChannelDetails.get_channel_details(changes.original_url) do - {:ok, %ChannelDetails{} = channel_details} -> - change_channel( - channel, + case SourceDetails.get_source_details(changes.original_url) do + {:ok, %SourceDetails{} = source_details} -> + change_source( + source, Map.merge(changes, %{ - name: channel_details.name, - channel_id: channel_details.id + name: source_details.name, + collection_id: source_details.id }) ) @@ -128,7 +128,7 @@ defmodule Pinchflat.MediaSource do Ecto.Changeset.add_error( changeset, :original_url, - "could not fetch channel details from URL", + "could not fetch source details from URL", error: runner_error ) end @@ -136,27 +136,27 @@ defmodule Pinchflat.MediaSource do defp commit_and_start_indexing(changeset) do case Repo.insert_or_update(changeset) do - {:ok, %Channel{} = channel} -> - maybe_run_indexing_task(changeset, channel) + {:ok, %Source{} = source} -> + maybe_run_indexing_task(changeset, source) - {:ok, channel} + {:ok, source} err -> err end end - defp maybe_run_indexing_task(changeset, channel) do + defp maybe_run_indexing_task(changeset, source) do case changeset.data do # If the changeset is new (not persisted), attempt indexing no matter what %{__meta__: %{state: :built}} -> - ChannelTasks.kickoff_indexing_task(channel) + SourceTasks.kickoff_indexing_task(source) # If the record has been persisted, only attempt indexing if the # indexing frequency has been changed %{__meta__: %{state: :loaded}} -> if Map.has_key?(changeset.changes, :index_frequency_minutes) do - ChannelTasks.kickoff_indexing_task(channel) + SourceTasks.kickoff_indexing_task(source) end end end diff --git a/lib/pinchflat/media_source/channel.ex b/lib/pinchflat/media_source/source.ex similarity index 51% rename from lib/pinchflat/media_source/channel.ex rename to lib/pinchflat/media_source/source.ex index e70030cf..b633bf85 100644 --- a/lib/pinchflat/media_source/channel.ex +++ b/lib/pinchflat/media_source/source.ex @@ -1,6 +1,6 @@ -defmodule Pinchflat.MediaSource.Channel do +defmodule Pinchflat.MediaSource.Source do @moduledoc """ - The Channel schema. + The Source schema. """ use Ecto.Schema @@ -9,29 +9,30 @@ defmodule Pinchflat.MediaSource.Channel do alias Pinchflat.Media.MediaItem alias Pinchflat.Profiles.MediaProfile - @allowed_fields ~w(name channel_id index_frequency_minutes original_url media_profile_id)a + @allowed_fields ~w(name collection_id collection_type index_frequency_minutes original_url media_profile_id)a @required_fields @allowed_fields -- ~w(index_frequency_minutes)a - schema "channels" do + schema "sources" do field :name, :string - field :channel_id, :string + field :collection_id, :string + field :collection_type, Ecto.Enum, values: [:channel, :playlist] field :index_frequency_minutes, :integer # This should only be used for user reference going forward - # as the channel_id should be used for all API calls + # as the collection_id should be used for all API calls field :original_url, :string belongs_to :media_profile, MediaProfile - has_many :media_items, MediaItem + has_many :media_items, MediaItem, foreign_key: :source_id timestamps(type: :utc_datetime) end @doc false - def changeset(channel, attrs) do - channel + def changeset(source, attrs) do + source |> cast(attrs, @allowed_fields) |> validate_required(@required_fields) - |> unique_constraint([:channel_id, :media_profile_id]) + |> unique_constraint([:collection_id, :media_profile_id]) end end diff --git a/lib/pinchflat/profiles/media_profile.ex b/lib/pinchflat/profiles/media_profile.ex index 39bb6db5..10964c9e 100644 --- a/lib/pinchflat/profiles/media_profile.ex +++ b/lib/pinchflat/profiles/media_profile.ex @@ -6,7 +6,7 @@ defmodule Pinchflat.Profiles.MediaProfile do use Ecto.Schema import Ecto.Changeset - alias Pinchflat.MediaSource.Channel + alias Pinchflat.MediaSource.Source @allowed_fields ~w( name @@ -27,7 +27,7 @@ defmodule Pinchflat.Profiles.MediaProfile do field :embed_subs, :boolean, default: true field :sub_langs, :string, default: "en" - has_many :channels, Channel + has_many :sources, Source timestamps(type: :utc_datetime) end diff --git a/lib/pinchflat/tasks.ex b/lib/pinchflat/tasks.ex index 052b1374..642864c8 100644 --- a/lib/pinchflat/tasks.ex +++ b/lib/pinchflat/tasks.ex @@ -8,7 +8,7 @@ defmodule Pinchflat.Tasks do alias Pinchflat.Tasks.Task alias Pinchflat.Media.MediaItem - alias Pinchflat.MediaSource.Channel + alias Pinchflat.MediaSource.Source @doc """ Returns the list of tasks. Returns [%Task{}, ...] @@ -57,7 +57,7 @@ defmodule Pinchflat.Tasks do @doc """ Creates a task. - Accepts map() | %Oban.Job{}, %Channel{} | %Oban.Job{}, %MediaItem{}. + Accepts map() | %Oban.Job{}, %Source{} | %Oban.Job{}, %MediaItem{}. Returns {:ok, %Task{}} | {:error, %Ecto.Changeset{}}. """ def create_task(attrs) do @@ -71,7 +71,7 @@ defmodule Pinchflat.Tasks do def create_task(%Oban.Job{} = job, attached_record) do attached_record_attr = case attached_record do - %Channel{} = channel -> %{channel_id: channel.id} + %Source{} = source -> %{source_id: source.id} %MediaItem{} = media_item -> %{media_item_id: media_item.id} end @@ -113,7 +113,7 @@ defmodule Pinchflat.Tasks do def delete_tasks_for(attached_record) do tasks = case attached_record do - %Channel{} = channel -> list_tasks_for(:channel_id, channel.id) + %Source{} = source -> list_tasks_for(:source_id, source.id) %MediaItem{} = media_item -> list_tasks_for(:media_item_id, media_item.id) end @@ -130,7 +130,7 @@ defmodule Pinchflat.Tasks do def delete_pending_tasks_for(attached_record) do tasks = case attached_record do - %Channel{} = channel -> list_pending_tasks_for(:channel_id, channel.id) + %Source{} = source -> list_pending_tasks_for(:source_id, source.id) %MediaItem{} = media_item -> list_pending_tasks_for(:media_item_id, media_item.id) end diff --git a/lib/pinchflat/tasks/channel_tasks.ex b/lib/pinchflat/tasks/source_tasks.ex similarity index 65% rename from lib/pinchflat/tasks/channel_tasks.ex rename to lib/pinchflat/tasks/source_tasks.ex index a2b1cd26..0d5aa086 100644 --- a/lib/pinchflat/tasks/channel_tasks.ex +++ b/lib/pinchflat/tasks/source_tasks.ex @@ -1,28 +1,28 @@ -defmodule Pinchflat.Tasks.ChannelTasks do +defmodule Pinchflat.Tasks.SourceTasks do @moduledoc """ - This module contains methods for managing tasks (workers) related to channels. + This module contains methods for managing tasks (workers) related to sources. """ alias Pinchflat.Tasks - alias Pinchflat.MediaSource.Channel + alias Pinchflat.MediaSource.Source alias Pinchflat.Workers.MediaIndexingWorker @doc """ - Starts tasks for indexing a channel's media. + Starts tasks for indexing a source's media. Returns {:ok, :should_not_index} | {:ok, %Task{}}. """ - def kickoff_indexing_task(%Channel{} = channel) do - Tasks.delete_pending_tasks_for(channel) + def kickoff_indexing_task(%Source{} = source) do + Tasks.delete_pending_tasks_for(source) - if channel.index_frequency_minutes <= 0 do + if source.index_frequency_minutes <= 0 do {:ok, :should_not_index} else - channel + source |> Map.take([:id]) # Schedule this one immediately, but future ones will be on an interval |> MediaIndexingWorker.new() - |> Tasks.create_job_with_task(channel) + |> Tasks.create_job_with_task(source) |> case do # This should never return {:error, :duplicate_job} since we just deleted # any pending tasks. I'm being assertive about it so it's obvious if I'm wrong diff --git a/lib/pinchflat/tasks/task.ex b/lib/pinchflat/tasks/task.ex index 9d53b600..34cf113f 100644 --- a/lib/pinchflat/tasks/task.ex +++ b/lib/pinchflat/tasks/task.ex @@ -7,11 +7,11 @@ defmodule Pinchflat.Tasks.Task do import Ecto.Changeset alias Pinchflat.Media.MediaItem - alias Pinchflat.MediaSource.Channel + alias Pinchflat.MediaSource.Source schema "tasks" do belongs_to :job, Oban.Job - belongs_to :channel, Channel + belongs_to :source, Source belongs_to :media_item, MediaItem timestamps(type: :utc_datetime) @@ -20,7 +20,7 @@ defmodule Pinchflat.Tasks.Task do @doc false def changeset(task, attrs) do task - |> cast(attrs, [:job_id, :channel_id, :media_item_id]) + |> cast(attrs, [:job_id, :source_id, :media_item_id]) |> validate_required([:job_id]) end end diff --git a/lib/pinchflat/workers/media_indexing_worker.ex b/lib/pinchflat/workers/media_indexing_worker.ex index 4869b0d2..166a4bd4 100644 --- a/lib/pinchflat/workers/media_indexing_worker.ex +++ b/lib/pinchflat/workers/media_indexing_worker.ex @@ -14,10 +14,10 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do @impl Oban.Worker @doc """ - The ID is that of a channel _record_, not a YouTube channel ID. Indexes - the provided channel, kicks off downloads for each new MediaItem, and + The ID is that of a source _record_, not a YouTube channel/playlist ID. Indexes + the provided source, kicks off downloads for each new MediaItem, and reschedules the job to run again in the future (as determined by the - channel's `index_frequency_minutes` field). + souce's `index_frequency_minutes` field). README: Re-scheduling here works a little different than you may expect. The reschedule time is relative to the time the job has actually _completed_. @@ -37,24 +37,24 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do Returns :ok | {:ok, %Task{}} """ - def perform(%Oban.Job{args: %{"id" => channel_id}}) do - channel = MediaSource.get_channel!(channel_id) + def perform(%Oban.Job{args: %{"id" => source_id}}) do + source = MediaSource.get_source!(source_id) - if channel.index_frequency_minutes <= 0 do + if source.index_frequency_minutes <= 0 do :ok else - index_media_and_reschedule(channel) + index_media_and_reschedule(source) end end - defp index_media_and_reschedule(channel) do - MediaSource.index_media_items(channel) - enqueue_video_downloads(channel) + defp index_media_and_reschedule(source) do + MediaSource.index_media_items(source) + enqueue_video_downloads(source) - channel + source |> Map.take([:id]) - |> MediaIndexingWorker.new(schedule_in: channel.index_frequency_minutes * 60) - |> Tasks.create_job_with_task(channel) + |> MediaIndexingWorker.new(schedule_in: source.index_frequency_minutes * 60) + |> Tasks.create_job_with_task(source) |> case do {:ok, task} -> {:ok, task} {:error, :duplicate_job} -> {:ok, :job_exists} @@ -67,8 +67,8 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do # or somehow got de-queued. # # I'm not sure of a case where this would happen, but it's cheap insurance. - defp enqueue_video_downloads(channel) do - channel + defp enqueue_video_downloads(source) do + source |> Media.list_pending_media_items_for() |> Enum.each(fn media_item -> media_item diff --git a/lib/pinchflat_web/controllers/media_sources/channel_controller.ex b/lib/pinchflat_web/controllers/media_sources/channel_controller.ex deleted file mode 100644 index 08de4f4c..00000000 --- a/lib/pinchflat_web/controllers/media_sources/channel_controller.ex +++ /dev/null @@ -1,75 +0,0 @@ -defmodule PinchflatWeb.MediaSources.ChannelController do - use PinchflatWeb, :controller - - alias Pinchflat.Profiles - alias Pinchflat.MediaSource - alias Pinchflat.MediaSource.Channel - - def index(conn, _params) do - channels = MediaSource.list_channels() - - render(conn, :index, channels: channels) - end - - def new(conn, _params) do - changeset = MediaSource.change_channel(%Channel{}) - - render(conn, :new, changeset: changeset, media_profiles: media_profiles()) - end - - def create(conn, %{"channel" => channel_params}) do - case MediaSource.create_channel(channel_params) do - {:ok, channel} -> - conn - |> put_flash(:info, "Channel created successfully.") - |> redirect(to: ~p"/media_sources/channels/#{channel}") - - {:error, %Ecto.Changeset{} = changeset} -> - render(conn, :new, changeset: changeset, media_profiles: media_profiles()) - end - end - - def show(conn, %{"id" => id}) do - channel = MediaSource.get_channel!(id) - - render(conn, :show, channel: channel) - end - - def edit(conn, %{"id" => id}) do - channel = MediaSource.get_channel!(id) - changeset = MediaSource.change_channel(channel) - - render(conn, :edit, channel: channel, changeset: changeset, media_profiles: media_profiles()) - end - - def update(conn, %{"id" => id, "channel" => channel_params}) do - channel = MediaSource.get_channel!(id) - - case MediaSource.update_channel(channel, channel_params) do - {:ok, channel} -> - conn - |> put_flash(:info, "Channel updated successfully.") - |> redirect(to: ~p"/media_sources/channels/#{channel}") - - {:error, %Ecto.Changeset{} = changeset} -> - render(conn, :edit, - channel: channel, - changeset: changeset, - media_profiles: media_profiles() - ) - end - end - - def delete(conn, %{"id" => id}) do - channel = MediaSource.get_channel!(id) - {:ok, _channel} = MediaSource.delete_channel(channel) - - conn - |> put_flash(:info, "Channel deleted successfully.") - |> redirect(to: ~p"/media_sources/channels") - end - - defp media_profiles do - Profiles.list_media_profiles() - end -end diff --git a/lib/pinchflat_web/controllers/media_sources/channel_html/edit.html.heex b/lib/pinchflat_web/controllers/media_sources/channel_html/edit.html.heex deleted file mode 100644 index c65d8108..00000000 --- a/lib/pinchflat_web/controllers/media_sources/channel_html/edit.html.heex +++ /dev/null @@ -1,12 +0,0 @@ -<.header> - Edit Channel <%= @channel.id %> - <:subtitle>Use this form to manage channel records in your database. - - -<.channel_form - changeset={@changeset} - media_profiles={@media_profiles} - action={~p"/media_sources/channels/#{@channel}"} -/> - -<.back navigate={~p"/media_sources/channels"}>Back to channels diff --git a/lib/pinchflat_web/controllers/media_sources/channel_html/index.html.heex b/lib/pinchflat_web/controllers/media_sources/channel_html/index.html.heex deleted file mode 100644 index b78a61b7..00000000 --- a/lib/pinchflat_web/controllers/media_sources/channel_html/index.html.heex +++ /dev/null @@ -1,28 +0,0 @@ -<.header> - Listing Channels - <:actions> - <.link href={~p"/media_sources/channels/new"}> - <.button>New Channel - - - - -<.table id="channels" rows={@channels} row_click={&JS.navigate(~p"/media_sources/channels/#{&1}")}> - <:col :let={channel} label="Name"><%= channel.name %> - <:col :let={channel} label="Channel"><%= channel.channel_id %> - <:action :let={channel}> -
- <.link navigate={~p"/media_sources/channels/#{channel}"}>Show -
- <.link navigate={~p"/media_sources/channels/#{channel}/edit"}>Edit - - <:action :let={channel}> - <.link - href={~p"/media_sources/channels/#{channel}"} - method="delete" - data-confirm="Are you sure?" - > - Delete - - - diff --git a/lib/pinchflat_web/controllers/media_sources/channel_html/new.html.heex b/lib/pinchflat_web/controllers/media_sources/channel_html/new.html.heex deleted file mode 100644 index b09a477b..00000000 --- a/lib/pinchflat_web/controllers/media_sources/channel_html/new.html.heex +++ /dev/null @@ -1,12 +0,0 @@ -<.header> - New Channel - <:subtitle>Use this form to manage channel records in your database. - - -<.channel_form - changeset={@changeset} - media_profiles={@media_profiles} - action={~p"/media_sources/channels"} -/> - -<.back navigate={~p"/media_sources/channels"}>Back to channels diff --git a/lib/pinchflat_web/controllers/media_sources/channel_html/show.html.heex b/lib/pinchflat_web/controllers/media_sources/channel_html/show.html.heex deleted file mode 100644 index 0f59e3b6..00000000 --- a/lib/pinchflat_web/controllers/media_sources/channel_html/show.html.heex +++ /dev/null @@ -1,17 +0,0 @@ -<.header> - Channel <%= @channel.id %> - <:subtitle>This is a channel record from your database. - <:actions> - <.link href={~p"/media_sources/channels/#{@channel}/edit"}> - <.button>Edit channel - - - - -<.list> - <:item title="Channel Name"><%= @channel.name %> - <:item title="Channel ID"><%= @channel.channel_id %> - <:item title="Original URL"><%= @channel.original_url %> - - -<.back navigate={~p"/media_sources/channels"}>Back to channels diff --git a/lib/pinchflat_web/controllers/media_sources/source_controller.ex b/lib/pinchflat_web/controllers/media_sources/source_controller.ex new file mode 100644 index 00000000..b90cf1e9 --- /dev/null +++ b/lib/pinchflat_web/controllers/media_sources/source_controller.ex @@ -0,0 +1,75 @@ +defmodule PinchflatWeb.MediaSources.SourceController do + use PinchflatWeb, :controller + + alias Pinchflat.Profiles + alias Pinchflat.MediaSource + alias Pinchflat.MediaSource.Source + + def index(conn, _params) do + sources = MediaSource.list_sources() + + render(conn, :index, sources: sources) + end + + def new(conn, _params) do + changeset = MediaSource.change_source(%Source{}) + + render(conn, :new, changeset: changeset, media_profiles: media_profiles()) + end + + def create(conn, %{"source" => source_params}) do + case MediaSource.create_source(source_params) do + {:ok, source} -> + conn + |> put_flash(:info, "Source created successfully.") + |> redirect(to: ~p"/media_sources/sources/#{source}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset, media_profiles: media_profiles()) + end + end + + def show(conn, %{"id" => id}) do + source = MediaSource.get_source!(id) + + render(conn, :show, source: source) + end + + def edit(conn, %{"id" => id}) do + source = MediaSource.get_source!(id) + changeset = MediaSource.change_source(source) + + render(conn, :edit, source: source, changeset: changeset, media_profiles: media_profiles()) + end + + def update(conn, %{"id" => id, "source" => source_params}) do + source = MediaSource.get_source!(id) + + case MediaSource.update_source(source, source_params) do + {:ok, source} -> + conn + |> put_flash(:info, "Source updated successfully.") + |> redirect(to: ~p"/media_sources/sources/#{source}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, + source: source, + changeset: changeset, + media_profiles: media_profiles() + ) + end + end + + def delete(conn, %{"id" => id}) do + source = MediaSource.get_source!(id) + {:ok, _source} = MediaSource.delete_source(source) + + conn + |> put_flash(:info, "Source deleted successfully.") + |> redirect(to: ~p"/media_sources/sources") + end + + defp media_profiles do + Profiles.list_media_profiles() + end +end diff --git a/lib/pinchflat_web/controllers/media_sources/channel_html.ex b/lib/pinchflat_web/controllers/media_sources/source_html.ex similarity index 77% rename from lib/pinchflat_web/controllers/media_sources/channel_html.ex rename to lib/pinchflat_web/controllers/media_sources/source_html.ex index 0feb1912..9531a597 100644 --- a/lib/pinchflat_web/controllers/media_sources/channel_html.ex +++ b/lib/pinchflat_web/controllers/media_sources/source_html.ex @@ -1,16 +1,16 @@ -defmodule PinchflatWeb.MediaSources.ChannelHTML do +defmodule PinchflatWeb.MediaSources.SourceHTML do use PinchflatWeb, :html - embed_templates "channel_html/*" + embed_templates "source_html/*" @doc """ - Renders a channel form. + Renders a source form. """ attr :changeset, Ecto.Changeset, required: true attr :action, :string, required: true attr :media_profiles, :list, required: true - def channel_form(assigns) + def source_form(assigns) def friendly_index_frequencies do [ diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/edit.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/edit.html.heex new file mode 100644 index 00000000..d1afc041 --- /dev/null +++ b/lib/pinchflat_web/controllers/media_sources/source_html/edit.html.heex @@ -0,0 +1,12 @@ +<.header> + Edit Source <%= @source.id %> + <:subtitle>Use this form to manage source records in your database. + + +<.source_form + changeset={@changeset} + media_profiles={@media_profiles} + action={~p"/media_sources/sources/#{@source}"} +/> + +<.back navigate={~p"/media_sources/sources"}>Back to sources diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex new file mode 100644 index 00000000..348873d0 --- /dev/null +++ b/lib/pinchflat_web/controllers/media_sources/source_html/index.html.heex @@ -0,0 +1,24 @@ +<.header> + Listing Sources + <:actions> + <.link href={~p"/media_sources/sources/new"}> + <.button>New Source + + + + +<.table id="sources" rows={@sources} row_click={&JS.navigate(~p"/media_sources/sources/#{&1}")}> + <:col :let={source} label="Name"><%= source.name %> + <:col :let={source} label="Source"><%= source.collection_id %> + <:action :let={source}> +
+ <.link navigate={~p"/media_sources/sources/#{source}"}>Show +
+ <.link navigate={~p"/media_sources/sources/#{source}/edit"}>Edit + + <:action :let={source}> + <.link href={~p"/media_sources/sources/#{source}"} method="delete" data-confirm="Are you sure?"> + Delete + + + diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/new.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/new.html.heex new file mode 100644 index 00000000..4ef57417 --- /dev/null +++ b/lib/pinchflat_web/controllers/media_sources/source_html/new.html.heex @@ -0,0 +1,12 @@ +<.header> + New Source + <:subtitle>Use this form to manage source records in your database. + + +<.source_form + changeset={@changeset} + media_profiles={@media_profiles} + action={~p"/media_sources/sources"} +/> + +<.back navigate={~p"/media_sources/sources"}>Back to sources diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex new file mode 100644 index 00000000..5ae21766 --- /dev/null +++ b/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex @@ -0,0 +1,17 @@ +<.header> + Source <%= @source.id %> + <:subtitle>This is a source record from your database. + <:actions> + <.link href={~p"/media_sources/sources/#{@source}/edit"}> + <.button>Edit source + + + + +<.list> + <:item title="Source Name"><%= @source.name %> + <:item title="Source ID"><%= @source.collection_id %> + <:item title="Original URL"><%= @source.original_url %> + + +<.back navigate={~p"/media_sources/sources"}>Back to sources diff --git a/lib/pinchflat_web/controllers/media_sources/channel_html/channel_form.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex similarity index 73% rename from lib/pinchflat_web/controllers/media_sources/channel_html/channel_form.html.heex rename to lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex index b8b4d4ed..f8a0a876 100644 --- a/lib/pinchflat_web/controllers/media_sources/channel_html/channel_form.html.heex +++ b/lib/pinchflat_web/controllers/media_sources/source_html/source_form.html.heex @@ -10,7 +10,8 @@ label="Media Profile" /> - <.input field={f[:original_url]} type="text" label="Channel URL" /> + <.input field={f[:collection_type]} type="text" label="Collection Type" /> + <.input field={f[:original_url]} type="text" label="Source URL" /> <.input field={f[:index_frequency_minutes]} @@ -20,6 +21,6 @@ /> <:actions> - <.button>Save Channel + <.button>Save Source diff --git a/lib/pinchflat_web/router.ex b/lib/pinchflat_web/router.ex index e0775ea3..fd06da34 100644 --- a/lib/pinchflat_web/router.ex +++ b/lib/pinchflat_web/router.ex @@ -22,7 +22,7 @@ defmodule PinchflatWeb.Router do resources "/media_profiles", MediaProfiles.MediaProfileController scope "/media_sources", MediaSources do - resources "/channels", ChannelController + resources "/sources", SourceController end end diff --git a/priv/repo/migrations/20240202040724_rename_channel_and_related_fields.exs b/priv/repo/migrations/20240202040724_rename_channel_and_related_fields.exs new file mode 100644 index 00000000..977756f5 --- /dev/null +++ b/priv/repo/migrations/20240202040724_rename_channel_and_related_fields.exs @@ -0,0 +1,45 @@ +defmodule Pinchflat.Repo.Migrations.RenameChannelAndRelatedFields do + use Ecto.Migration + + def change do + # Creation + create table(:sources) do + add :name, :string, null: false + add :collection_type, :string, null: false + add :collection_id, :string, null: false + add :original_url, :string, null: false + add :media_profile_id, references(:media_profiles, on_delete: :restrict), null: false + add :index_frequency_minutes, :integer, default: 60 * 24, null: false + + timestamps(type: :utc_datetime) + end + + alter table(:media_items) do + add :source_id, references(:sources, on_delete: :restrict), null: false + end + + alter table(:tasks) do + # `restrict` because we need to be sure to delete pending tasks when a source is deleted + add :source_id, references(:sources, on_delete: :restrict), null: true + end + + create index(:sources, [:media_profile_id]) + create unique_index(:sources, [:collection_id, :media_profile_id]) + + create index(:media_items, [:source_id]) + create unique_index(:media_items, [:media_id, :source_id]) + + # Deletion + drop index(:media_items, [:channel_id]) + drop unique_index(:media_items, [:media_id, :channel_id]) + + alter table(:tasks) do + # `restrict` because we need to be sure to delete pending tasks when a source is deleted + remove :channel_id, references(:channels, on_delete: :restrict), null: true + end + + alter table(:media_items) do + remove :channel_id, references(:channels, on_delete: :restrict), null: true + end + end +end diff --git a/priv/repo/migrations/20240202043042_drop_channels_table.exs b/priv/repo/migrations/20240202043042_drop_channels_table.exs new file mode 100644 index 00000000..6397b19e --- /dev/null +++ b/priv/repo/migrations/20240202043042_drop_channels_table.exs @@ -0,0 +1,21 @@ +defmodule Pinchflat.Repo.Migrations.DropChannelsTable do + use Ecto.Migration + + def up do + drop table(:channels) + end + + def down do + create table(:channels) do + add :name, :string, null: false + add :channel_id, :string, null: false + add :original_url, :string, null: false + add :media_profile_id, references(:media_profiles, on_delete: :restrict), null: false + + timestamps(type: :utc_datetime) + end + + create index(:channels, [:media_profile_id]) + create unique_index(:channels, [:channel_id, :media_profile_id]) + end +end diff --git a/test/pinchflat/media_client/backends/yt_dlp/channel_test.exs b/test/pinchflat/media_client/backends/yt_dlp/channel_test.exs deleted file mode 100644 index 3f9d52f5..00000000 --- a/test/pinchflat/media_client/backends/yt_dlp/channel_test.exs +++ /dev/null @@ -1,45 +0,0 @@ -defmodule Pinchflat.MediaClient.Backends.YtDlp.ChannelTest do - use ExUnit.Case, async: true - import Mox - - alias Pinchflat.MediaClient.ChannelDetails - alias Pinchflat.MediaClient.Backends.YtDlp.Channel - - @channel_url "https://www.youtube.com/c/TheUselessTrials" - - setup :verify_on_exit! - - describe "get_channel_details/1" do - test "it returns a %ChannelDetails{} with data on success" do - expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> - {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"} - end) - - assert {:ok, res} = Channel.get_channel_details(@channel_url) - assert %ChannelDetails{id: "UCQH2", name: "TheUselessTrials"} = res - end - - test "it passes the expected args to the backend runner" do - expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot -> - assert opts == [playlist_end: 1] - assert ot == "%(.{channel,channel_id})j" - - {:ok, "{}"} - end) - - assert {:ok, _} = Channel.get_channel_details(@channel_url) - end - - test "it returns an error if the runner returns an error" do - expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:error, "Big issue", 1} end) - - assert {:error, "Big issue", 1} = Channel.get_channel_details(@channel_url) - end - - test "it returns an error if the output is not JSON" do - expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "Not JSON"} end) - - assert {:error, %Jason.DecodeError{}} = Channel.get_channel_details(@channel_url) - end - end -end diff --git a/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs b/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs index 3acae204..3c8bfe77 100644 --- a/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs +++ b/test/pinchflat/media_client/backends/yt_dlp/video_collection_test.exs @@ -2,13 +2,10 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do use ExUnit.Case, async: true import Mox + alias Pinchflat.MediaClient.SourceDetails alias Pinchflat.MediaClient.Backends.YtDlp.VideoCollection - @channel_url "https://www.youtube.com/@TheUselessTrials" - - defmodule VideoCollectionUser do - use VideoCollection - end + @channel_url "https://www.youtube.com/c/TheUselessTrials" setup :verify_on_exit! @@ -16,7 +13,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do test "returns a list of video ids with no blank elements" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "id1\nid2\n\nid3\n"} end) - assert {:ok, ["id1", "id2", "id3"]} = VideoCollectionUser.get_video_ids(@channel_url) + assert {:ok, ["id1", "id2", "id3"]} = VideoCollection.get_video_ids(@channel_url) end test "it passes the expected default args" do @@ -27,7 +24,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do {:ok, ""} end) - assert {:ok, _} = VideoCollectionUser.get_video_ids(@channel_url) + assert {:ok, _} = VideoCollection.get_video_ids(@channel_url) end test "it passes the expected custom args" do @@ -37,13 +34,47 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do {:ok, ""} end) - assert {:ok, _} = VideoCollectionUser.get_video_ids(@channel_url, [:custom_arg]) + assert {:ok, _} = VideoCollection.get_video_ids(@channel_url, [:custom_arg]) end test "returns the error straight through when the command fails" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:error, "Big issue", 1} end) - assert {:error, "Big issue", 1} = VideoCollectionUser.get_video_ids(@channel_url) + assert {:error, "Big issue", 1} = VideoCollection.get_video_ids(@channel_url) + end + end + + describe "get_source_details/1" do + test "it returns a %SourceDetails{} with data on success" do + expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> + {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"} + end) + + assert {:ok, res} = VideoCollection.get_source_details(@channel_url) + assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} = res + end + + test "it passes the expected args to the backend runner" do + expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot -> + assert opts == [:skip_download, playlist_end: 1] + assert ot == "%(.{channel,channel_id})j" + + {:ok, "{}"} + end) + + assert {:ok, _} = VideoCollection.get_source_details(@channel_url) + end + + test "it returns an error if the runner returns an error" do + expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:error, "Big issue", 1} end) + + assert {:error, "Big issue", 1} = VideoCollection.get_source_details(@channel_url) + end + + test "it returns an error if the output is not JSON" do + expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "Not JSON"} end) + + assert {:error, %Jason.DecodeError{}} = VideoCollection.get_source_details(@channel_url) end end end diff --git a/test/pinchflat/media_client/channel_details_test.exs b/test/pinchflat/media_client/source_details_test.exs similarity index 63% rename from test/pinchflat/media_client/channel_details_test.exs rename to test/pinchflat/media_client/source_details_test.exs index 920fc029..244e65d7 100644 --- a/test/pinchflat/media_client/channel_details_test.exs +++ b/test/pinchflat/media_client/source_details_test.exs @@ -1,8 +1,8 @@ -defmodule Pinchflat.MediaClient.ChannelDetailsTest do +defmodule Pinchflat.MediaClient.SourceDetailsTest do use ExUnit.Case, async: true import Mox - alias Pinchflat.MediaClient.ChannelDetails + alias Pinchflat.MediaClient.SourceDetails @channel_url "https://www.youtube.com/c/TheUselessTrials" @@ -10,21 +10,21 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do describe "new/2" do test "it returns a struct with the given values" do - assert %ChannelDetails{id: "UCQH2", name: "TheUselessTrials"} = - ChannelDetails.new("UCQH2", "TheUselessTrials") + assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} = + SourceDetails.new("UCQH2", "TheUselessTrials") end end - describe "get_channel_details/2" do + describe "get_source_details/2" do test "it passes the expected arguments to the backend" do expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot -> - assert opts == [playlist_end: 1] + assert opts == [:skip_download, playlist_end: 1] assert ot == "%(.{channel,channel_id})j" {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"} end) - assert {:ok, _} = ChannelDetails.get_channel_details(@channel_url) + assert {:ok, _} = SourceDetails.get_source_details(@channel_url) end test "it returns a struct composed of the returned data" do @@ -32,8 +32,8 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"} end) - assert {:ok, res} = ChannelDetails.get_channel_details(@channel_url) - assert %ChannelDetails{id: "UCQH2", name: "TheUselessTrials"} = res + assert {:ok, res} = SourceDetails.get_source_details(@channel_url) + assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} = res end end @@ -46,7 +46,7 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do {:ok, ""} end) - assert {:ok, _} = ChannelDetails.get_video_ids(@channel_url) + assert {:ok, _} = SourceDetails.get_video_ids(@channel_url) end test "it returns a list of strings" do @@ -54,7 +54,7 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do {:ok, "video1\nvideo2\nvideo3"} end) - assert {:ok, ["video1", "video2", "video3"]} = ChannelDetails.get_video_ids(@channel_url) + assert {:ok, ["video1", "video2", "video3"]} = SourceDetails.get_video_ids(@channel_url) end end end diff --git a/test/pinchflat/media_client/video_downloader_test.exs b/test/pinchflat/media_client/video_downloader_test.exs index 50d19fed..3d315309 100644 --- a/test/pinchflat/media_client/video_downloader_test.exs +++ b/test/pinchflat/media_client/video_downloader_test.exs @@ -11,7 +11,7 @@ defmodule Pinchflat.MediaClient.VideoDownloaderTest do media_item = Repo.preload( media_item_fixture(%{title: nil, media_filepath: nil}), - [:metadata, channel: :media_profile] + [:metadata, source: :media_profile] ) {:ok, %{media_item: media_item}} diff --git a/test/pinchflat/media_source_test.exs b/test/pinchflat/media_source_test.exs index f46cfb32..e113df5d 100644 --- a/test/pinchflat/media_source_test.exs +++ b/test/pinchflat/media_source_test.exs @@ -7,82 +7,85 @@ defmodule Pinchflat.MediaSourceTest do alias Pinchflat.MediaSource alias Pinchflat.Media.MediaItem - alias Pinchflat.MediaSource.Channel + alias Pinchflat.MediaSource.Source alias Pinchflat.Workers.MediaIndexingWorker - @invalid_channel_attrs %{name: nil, channel_id: nil} + @invalid_source_attrs %{name: nil, collection_id: nil} setup :verify_on_exit! - describe "list_channels/0" do - test "it returns all channels" do - channel = channel_fixture() - assert MediaSource.list_channels() == [channel] + describe "list_sources/0" do + test "it returns all sources" do + source = source_fixture() + assert MediaSource.list_sources() == [source] end end - describe "get_channel!/1" do - test "it returns the channel with given id" do - channel = channel_fixture() - assert MediaSource.get_channel!(channel.id) == channel + describe "get_source!/1" do + test "it returns the source with given id" do + source = source_fixture() + assert MediaSource.get_source!(source.id) == source end end - describe "create_channel/1" do - test "creates a channel and adds name + ID from runner response" do + describe "create_source/1" do + test "creates a source and adds name + ID from runner response" do expect(YtDlpRunnerMock, :run, &runner_function_mock/3) valid_attrs = %{ media_profile_id: media_profile_fixture().id, - original_url: "https://www.youtube.com/channel/abc123" + original_url: "https://www.youtube.com/channel/abc123", + collection_type: "channel" } - assert {:ok, %Channel{} = channel} = MediaSource.create_channel(valid_attrs) - assert channel.name == "some name" - assert String.starts_with?(channel.channel_id, "some_channel_id_") + assert {:ok, %Source{} = source} = MediaSource.create_source(valid_attrs) + assert source.name == "some name" + assert String.starts_with?(source.collection_id, "some_source_id_") end test "creation with invalid data returns error changeset" do - assert {:error, %Ecto.Changeset{}} = MediaSource.create_channel(@invalid_channel_attrs) + assert {:error, %Ecto.Changeset{}} = MediaSource.create_source(@invalid_source_attrs) end - test "creation enforces uniqueness of channel_id scoped to the media_profile" do + test "creation enforces uniqueness of source_id scoped to the media_profile" do expect(YtDlpRunnerMock, :run, 2, fn _url, _opts, _ot -> {:ok, Phoenix.json_library().encode!(%{ channel: "some name", - channel_id: "some_channel_id_12345678" + channel_id: "some_source_id_12345678" })} end) valid_once_attrs = %{ media_profile_id: media_profile_fixture().id, - original_url: "https://www.youtube.com/channel/abc123" + original_url: "https://www.youtube.com/channel/abc123", + collection_type: "channel" } - assert {:ok, %Channel{}} = MediaSource.create_channel(valid_once_attrs) - assert {:error, %Ecto.Changeset{}} = MediaSource.create_channel(valid_once_attrs) + assert {:ok, %Source{}} = MediaSource.create_source(valid_once_attrs) + assert {:error, %Ecto.Changeset{}} = MediaSource.create_source(valid_once_attrs) end - test "creation lets you duplicate channel_ids as long as the media profile is different" do + test "creation lets you duplicate collection_ids as long as the media profile is different" do expect(YtDlpRunnerMock, :run, 2, fn _url, _opts, _ot -> {:ok, Phoenix.json_library().encode!(%{ channel: "some name", - channel_id: "some_channel_id_12345678" + channel_id: "some_source_id_12345678" })} end) valid_attrs = %{ name: "some name", - original_url: "https://www.youtube.com/channel/abc123" + original_url: "https://www.youtube.com/channel/abc123", + collection_type: "channel" } - channel_1_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id}) - channel_2_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id}) + source_1_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id}) + source_2_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id}) - assert {:ok, %Channel{}} = MediaSource.create_channel(channel_1_attrs) - assert {:ok, %Channel{}} = MediaSource.create_channel(channel_2_attrs) + assert {:ok, %Source{}} = MediaSource.create_source(source_1_attrs) + assert {:ok, %Source{}} = MediaSource.create_source(source_2_attrs) end test "creation will schedule the indexing task" do @@ -90,12 +93,13 @@ defmodule Pinchflat.MediaSourceTest do valid_attrs = %{ media_profile_id: media_profile_fixture().id, - original_url: "https://www.youtube.com/channel/abc123" + original_url: "https://www.youtube.com/channel/abc123", + collection_type: "channel" } - assert {:ok, %Channel{} = channel} = MediaSource.create_channel(valid_attrs) + assert {:ok, %Source{} = source} = MediaSource.create_source(valid_attrs) - assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) + assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id}) end end @@ -103,181 +107,181 @@ defmodule Pinchflat.MediaSourceTest do setup do stub(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1\nvideo2\nvideo3"} end) - {:ok, [channel: channel_fixture()]} + {:ok, [source: source_fixture()]} end - test "it creates a media_item record for each media ID returned", %{channel: channel} do - assert media_items = MediaSource.index_media_items(channel) + test "it creates a media_item record for each media ID returned", %{source: source} do + assert media_items = MediaSource.index_media_items(source) assert Enum.count(media_items) == 3 assert ["video1", "video2", "video3"] == Enum.map(media_items, & &1.media_id) assert Enum.all?(media_items, fn %MediaItem{} -> true end) end - test "it attaches all media_items to the given channel", %{channel: channel} do - channel_id = channel.id - assert media_items = MediaSource.index_media_items(channel) + test "it attaches all media_items to the given source", %{source: source} do + source_id = source.id + assert media_items = MediaSource.index_media_items(source) assert Enum.count(media_items) == 3 - assert Enum.all?(media_items, fn %MediaItem{channel_id: ^channel_id} -> true end) + assert Enum.all?(media_items, fn %MediaItem{source_id: ^source_id} -> true end) end - test "it won't duplicate media_items based on media_id and channel", %{channel: channel} do - _first_run = MediaSource.index_media_items(channel) - _duplicate_run = MediaSource.index_media_items(channel) + test "it won't duplicate media_items based on media_id and source", %{source: source} do + _first_run = MediaSource.index_media_items(source) + _duplicate_run = MediaSource.index_media_items(source) - media_items = Repo.preload(channel, :media_items).media_items + media_items = Repo.preload(source, :media_items).media_items assert Enum.count(media_items) == 3 end - test "it can duplicate media_ids for different channels", %{channel: channel} do - other_channel = channel_fixture() + test "it can duplicate media_ids for different sources", %{source: source} do + other_source = source_fixture() - media_items = MediaSource.index_media_items(channel) - media_items_other_channel = MediaSource.index_media_items(other_channel) + media_items = MediaSource.index_media_items(source) + media_items_other_source = MediaSource.index_media_items(other_source) assert Enum.count(media_items) == 3 - assert Enum.count(media_items_other_channel) == 3 + assert Enum.count(media_items_other_source) == 3 assert Enum.map(media_items, & &1.media_id) == - Enum.map(media_items_other_channel, & &1.media_id) + Enum.map(media_items_other_source, & &1.media_id) end - test "it returns a list of media_items or changesets", %{channel: channel} do - first_run = MediaSource.index_media_items(channel) - duplicate_run = MediaSource.index_media_items(channel) + test "it returns a list of media_items or changesets", %{source: source} do + first_run = MediaSource.index_media_items(source) + duplicate_run = MediaSource.index_media_items(source) assert Enum.all?(first_run, fn %MediaItem{} -> true end) assert Enum.all?(duplicate_run, fn %Ecto.Changeset{} -> true end) end end - describe "update_channel/2" do - test "updates with valid data updates the channel" do - channel = channel_fixture() + describe "update_source/2" do + test "updates with valid data updates the source" do + source = source_fixture() update_attrs = %{name: "some updated name"} - assert {:ok, %Channel{} = channel} = MediaSource.update_channel(channel, update_attrs) - assert channel.name == "some updated name" + assert {:ok, %Source{} = source} = MediaSource.update_source(source, update_attrs) + assert source.name == "some updated name" end - test "updating the original_url will re-fetch the channel details" do + test "updating the original_url will re-fetch the source details" do expect(YtDlpRunnerMock, :run, &runner_function_mock/3) - channel = channel_fixture() + source = source_fixture() update_attrs = %{original_url: "https://www.youtube.com/channel/abc123"} - assert {:ok, %Channel{} = channel} = MediaSource.update_channel(channel, update_attrs) - assert channel.name == "some name" - assert String.starts_with?(channel.channel_id, "some_channel_id_") + assert {:ok, %Source{} = source} = MediaSource.update_source(source, update_attrs) + assert source.name == "some name" + assert String.starts_with?(source.collection_id, "some_source_id_") end - test "not updating the original_url will not re-fetch the channel details" do + test "not updating the original_url will not re-fetch the source details" do expect(YtDlpRunnerMock, :run, 0, &runner_function_mock/3) - channel = channel_fixture() + source = source_fixture() update_attrs = %{name: "some updated name"} - assert {:ok, %Channel{}} = MediaSource.update_channel(channel, update_attrs) + assert {:ok, %Source{}} = MediaSource.update_source(source, update_attrs) end test "updating the index frequency will re-schedule the indexing task" do - channel = channel_fixture() + source = source_fixture() update_attrs = %{index_frequency_minutes: 123} - assert {:ok, %Channel{} = channel} = MediaSource.update_channel(channel, update_attrs) - assert channel.index_frequency_minutes == 123 - assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) + assert {:ok, %Source{} = source} = MediaSource.update_source(source, update_attrs) + assert source.index_frequency_minutes == 123 + assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id}) end test "not updating the index frequency will not re-schedule the indexing task" do - channel = channel_fixture() + source = source_fixture() update_attrs = %{name: "some updated name"} - assert {:ok, %Channel{}} = MediaSource.update_channel(channel, update_attrs) - refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) + assert {:ok, %Source{}} = MediaSource.update_source(source, update_attrs) + refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id}) end test "updates with invalid data returns error changeset" do - channel = channel_fixture() + source = source_fixture() assert {:error, %Ecto.Changeset{}} = - MediaSource.update_channel(channel, @invalid_channel_attrs) + MediaSource.update_source(source, @invalid_source_attrs) - assert channel == MediaSource.get_channel!(channel.id) + assert source == MediaSource.get_source!(source.id) end end - describe "delete_channel/1" do - test "it deletes the channel" do - channel = channel_fixture() - assert {:ok, %Channel{}} = MediaSource.delete_channel(channel) - assert_raise Ecto.NoResultsError, fn -> MediaSource.get_channel!(channel.id) end + describe "delete_source/1" do + test "it deletes the source" do + source = source_fixture() + assert {:ok, %Source{}} = MediaSource.delete_source(source) + assert_raise Ecto.NoResultsError, fn -> MediaSource.get_source!(source.id) end end - test "it returns a channel changeset" do - channel = channel_fixture() - assert %Ecto.Changeset{} = MediaSource.change_channel(channel) + test "it returns a source changeset" do + source = source_fixture() + assert %Ecto.Changeset{} = MediaSource.change_source(source) end test "deletion also deletes all associated tasks" do - channel = channel_fixture() - task = task_fixture(channel_id: channel.id) + source = source_fixture() + task = task_fixture(source_id: source.id) - assert {:ok, %Channel{}} = MediaSource.delete_channel(channel) + assert {:ok, %Source{}} = MediaSource.delete_source(source) assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task) end end end - describe "change_channel/2" do + describe "change_source/2" do test "it returns a changeset" do - channel = channel_fixture() + source = source_fixture() - assert %Ecto.Changeset{} = MediaSource.change_channel(channel) + assert %Ecto.Changeset{} = MediaSource.change_source(source) end end - describe "change_channel_from_url/2" do + describe "change_source_from_url/2" do test "it returns a changeset" do stub(YtDlpRunnerMock, :run, &runner_function_mock/3) - channel = channel_fixture() + source = source_fixture() - assert %Ecto.Changeset{} = MediaSource.change_channel_from_url(channel, %{}) + assert %Ecto.Changeset{} = MediaSource.change_source_from_url(source, %{}) end - test "it does not fetch channel details if the original_url isn't in the changeset" do + test "it does not fetch source details if the original_url isn't in the changeset" do expect(YtDlpRunnerMock, :run, 0, &runner_function_mock/3) - changeset = MediaSource.change_channel_from_url(%Channel{}, %{name: "some updated name"}) + changeset = MediaSource.change_source_from_url(%Source{}, %{name: "some updated name"}) assert %Ecto.Changeset{} = changeset end - test "it fetches channel details if the original_url is in the changeset" do + test "it fetches source details if the original_url is in the changeset" do expect(YtDlpRunnerMock, :run, &runner_function_mock/3) changeset = - MediaSource.change_channel_from_url(%Channel{}, %{ + MediaSource.change_source_from_url(%Source{}, %{ original_url: "https://www.youtube.com/channel/abc123" }) assert %Ecto.Changeset{} = changeset end - test "it adds channel details to the changeset, keeping the orignal details" do + test "it adds source details to the changeset, keeping the orignal details" do expect(YtDlpRunnerMock, :run, &runner_function_mock/3) media_profile = media_profile_fixture() media_profile_id = media_profile.id changeset = - MediaSource.change_channel_from_url(%Channel{}, %{ + MediaSource.change_source_from_url(%Source{}, %{ original_url: "https://www.youtube.com/channel/abc123", media_profile_id: media_profile.id }) assert %Ecto.Changeset{} = changeset - assert String.starts_with?(changeset.changes.channel_id, "some_channel_id_") + assert String.starts_with?(changeset.changes.collection_id, "some_source_id_") assert %{ name: "some name", @@ -292,12 +296,12 @@ defmodule Pinchflat.MediaSourceTest do end) changeset = - MediaSource.change_channel_from_url(%Channel{}, %{ + MediaSource.change_source_from_url(%Source{}, %{ original_url: "https://www.youtube.com/channel/abc123" }) assert %Ecto.Changeset{} = changeset - assert errors_on(changeset).original_url == ["could not fetch channel details from URL"] + assert errors_on(changeset).original_url == ["could not fetch source details from URL"] end end @@ -306,7 +310,7 @@ defmodule Pinchflat.MediaSourceTest do :ok, Phoenix.json_library().encode!(%{ channel: "some name", - channel_id: "some_channel_id_#{:rand.uniform(1_000_000)}" + channel_id: "some_source_id_#{:rand.uniform(1_000_000)}" }) } end diff --git a/test/pinchflat/media_test.exs b/test/pinchflat/media_test.exs index d63c77dd..13d7b4b2 100644 --- a/test/pinchflat/media_test.exs +++ b/test/pinchflat/media_test.exs @@ -30,23 +30,23 @@ defmodule Pinchflat.MediaTest do end describe "list_pending_media_items_for/1" do - test "it returns pending media_items for a given channel" do - channel = channel_fixture() - media_item = media_item_fixture(%{channel_id: channel.id, media_filepath: nil}) + test "it returns pending media_items for a given source" do + source = source_fixture() + media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil}) - assert Media.list_pending_media_items_for(channel) == [media_item] + assert Media.list_pending_media_items_for(source) == [media_item] end test "it does not return media_items with media_filepath" do - channel = channel_fixture() + source = source_fixture() _media_item = media_item_fixture(%{ - channel_id: channel.id, + source_id: source.id, media_filepath: "/video/#{Faker.File.file_name(:video)}" }) - assert Media.list_pending_media_items_for(channel) == [] + assert Media.list_pending_media_items_for(source) == [] end end @@ -63,7 +63,7 @@ defmodule Pinchflat.MediaTest do media_id: Faker.String.base64(12), title: Faker.Commerce.product_name(), media_filepath: "/video/#{Faker.File.file_name(:video)}", - channel_id: channel_fixture().id + source_id: source_fixture().id } assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs) @@ -85,7 +85,7 @@ defmodule Pinchflat.MediaTest do media_id: Faker.String.base64(12), title: Faker.Commerce.product_name(), media_filepath: "/video/#{Faker.File.file_name(:video)}", - channel_id: channel_fixture().id + source_id: source_fixture().id } assert {:ok, %MediaItem{} = media_item} = Media.update_media_item(media_item, update_attrs) diff --git a/test/pinchflat/tasks/channel_tasks_test.exs b/test/pinchflat/tasks/channel_tasks_test.exs deleted file mode 100644 index 23145232..00000000 --- a/test/pinchflat/tasks/channel_tasks_test.exs +++ /dev/null @@ -1,45 +0,0 @@ -defmodule Pinchflat.Tasks.ChannelTasksTest do - use Pinchflat.DataCase - - import Pinchflat.TasksFixtures - import Pinchflat.MediaSourceFixtures - - alias Pinchflat.Tasks.Task - alias Pinchflat.Tasks.ChannelTasks - alias Pinchflat.Workers.MediaIndexingWorker - - describe "kickoff_indexing_task/1" do - test "it does not schedule a job if the interval is <= 0" do - channel = channel_fixture(index_frequency_minutes: -1) - - assert {:ok, :should_not_index} = ChannelTasks.kickoff_indexing_task(channel) - - refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) - end - - test "it schedules a job if the interval is > 0" do - channel = channel_fixture(index_frequency_minutes: 1) - - assert {:ok, _} = ChannelTasks.kickoff_indexing_task(channel) - - assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) - end - - test "it creates and attaches a task if the interval is > 0" do - channel = channel_fixture(index_frequency_minutes: 1) - - assert {:ok, %Task{} = task} = ChannelTasks.kickoff_indexing_task(channel) - - assert task.channel_id == channel.id - end - - test "it deletes any pending tasks for the channel" do - channel = channel_fixture() - task = task_fixture(channel_id: channel.id) - - assert {:ok, _} = ChannelTasks.kickoff_indexing_task(channel) - - assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task) end - end - end -end diff --git a/test/pinchflat/tasks/source_tasks_test.exs b/test/pinchflat/tasks/source_tasks_test.exs new file mode 100644 index 00000000..1e9c38f3 --- /dev/null +++ b/test/pinchflat/tasks/source_tasks_test.exs @@ -0,0 +1,45 @@ +defmodule Pinchflat.Tasks.SourceTasksTest do + use Pinchflat.DataCase + + import Pinchflat.TasksFixtures + import Pinchflat.MediaSourceFixtures + + alias Pinchflat.Tasks.Task + alias Pinchflat.Tasks.SourceTasks + alias Pinchflat.Workers.MediaIndexingWorker + + describe "kickoff_indexing_task/1" do + test "it does not schedule a job if the interval is <= 0" do + source = source_fixture(index_frequency_minutes: -1) + + assert {:ok, :should_not_index} = SourceTasks.kickoff_indexing_task(source) + + refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id}) + end + + test "it schedules a job if the interval is > 0" do + source = source_fixture(index_frequency_minutes: 1) + + assert {:ok, _} = SourceTasks.kickoff_indexing_task(source) + + assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id}) + end + + test "it creates and attaches a task if the interval is > 0" do + source = source_fixture(index_frequency_minutes: 1) + + assert {:ok, %Task{} = task} = SourceTasks.kickoff_indexing_task(source) + + assert task.source_id == source.id + end + + test "it deletes any pending tasks for the source" do + source = source_fixture() + task = task_fixture(source_id: source.id) + + assert {:ok, _} = SourceTasks.kickoff_indexing_task(source) + + assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task) end + end + end +end diff --git a/test/pinchflat/tasks_test.exs b/test/pinchflat/tasks_test.exs index fbcc6707..d89c5b00 100644 --- a/test/pinchflat/tasks_test.exs +++ b/test/pinchflat/tasks_test.exs @@ -21,11 +21,11 @@ defmodule Pinchflat.TasksTest do end test "it does not delete the other record when a job gets deleted" do - task = Repo.preload(task_fixture(), [:channel, :job]) + task = Repo.preload(task_fixture(), [:source, :job]) {:ok, _} = Repo.delete(task.job) - assert Repo.reload!(task.channel) + assert Repo.reload!(task.source) end end @@ -40,14 +40,14 @@ defmodule Pinchflat.TasksTest do test "it lets you specify which record type/ID to join on" do task = task_fixture() - assert Tasks.list_tasks_for(:channel_id, task.channel_id) == [task] + assert Tasks.list_tasks_for(:source_id, task.source_id) == [task] end test "it lets you specify which job states to include" do task = task_fixture() - assert Tasks.list_tasks_for(:channel_id, task.channel_id, [:available]) == [task] - assert Tasks.list_tasks_for(:channel_id, task.channel_id, [:cancelled]) == [] + assert Tasks.list_tasks_for(:source_id, task.source_id, [:available]) == [task] + assert Tasks.list_tasks_for(:source_id, task.source_id, [:cancelled]) == [] end end @@ -55,14 +55,14 @@ defmodule Pinchflat.TasksTest do test "it lists pending tasks" do task = task_fixture() - assert Tasks.list_pending_tasks_for(:channel_id, task.channel_id) == [task] + assert Tasks.list_pending_tasks_for(:source_id, task.source_id) == [task] end test "it does not list non-pending tasks" do task = Repo.preload(task_fixture(), :job) :ok = Oban.cancel_job(task.job) - assert Tasks.list_pending_tasks_for(:channel_id, task.channel_id) == [] + assert Tasks.list_pending_tasks_for(:source_id, task.source_id) == [] end end @@ -84,14 +84,14 @@ defmodule Pinchflat.TasksTest do assert {:error, %Ecto.Changeset{}} = Tasks.create_task(@invalid_attrs) end - test "accepts a job and channel" do + test "accepts a job and source" do job = job_fixture() - channel = channel_fixture() + source = source_fixture() - assert {:ok, %Task{} = task} = Tasks.create_task(job, channel) + assert {:ok, %Task{} = task} = Tasks.create_task(job, source) assert task.job_id == job.id - assert task.channel_id == channel.id + assert task.source_id == source.id end test "accepts a job and media item" do @@ -115,25 +115,25 @@ defmodule Pinchflat.TasksTest do end test "it creates a task record if successful" do - channel = channel_fixture() + source = source_fixture() - assert {:ok, %Task{} = task} = Tasks.create_job_with_task(TestJobWorker.new(%{}), channel) + assert {:ok, %Task{} = task} = Tasks.create_job_with_task(TestJobWorker.new(%{}), source) - assert task.channel_id == channel.id + assert task.source_id == source.id end test "it returns an error if the job already exists" do - channel = channel_fixture() + source = source_fixture() job = TestJobWorker.new(%{foo: "bar"}, unique: [period: :infinity]) - assert {:ok, %Task{}} = Tasks.create_job_with_task(job, channel) - assert {:error, :duplicate_job} = Tasks.create_job_with_task(job, channel) + assert {:ok, %Task{}} = Tasks.create_job_with_task(job, source) + assert {:error, :duplicate_job} = Tasks.create_job_with_task(job, source) end test "it returns an error if the job fails to enqueue" do - channel = channel_fixture() + source = source_fixture() - assert {:error, %Ecto.Changeset{}} = Tasks.create_job_with_task(%Ecto.Changeset{}, channel) + assert {:error, %Ecto.Changeset{}} = Tasks.create_job_with_task(%Ecto.Changeset{}, source) end end @@ -155,11 +155,11 @@ defmodule Pinchflat.TasksTest do end describe "delete_tasks_for/1" do - test "it deletes tasks attached to a channel" do - channel = channel_fixture() - task = task_fixture(channel_id: channel.id) + test "it deletes tasks attached to a source" do + source = source_fixture() + task = task_fixture(source_id: source.id) - assert :ok = Tasks.delete_tasks_for(channel) + assert :ok = Tasks.delete_tasks_for(source) assert_raise Ecto.NoResultsError, fn -> Tasks.get_task!(task.id) end end @@ -173,20 +173,20 @@ defmodule Pinchflat.TasksTest do end describe "delete_pending_tasks_for/1" do - test "it deletes pending tasks attached to a channel" do - channel = channel_fixture() - task = task_fixture(channel_id: channel.id) + test "it deletes pending tasks attached to a source" do + source = source_fixture() + task = task_fixture(source_id: source.id) - assert :ok = Tasks.delete_pending_tasks_for(channel) + assert :ok = Tasks.delete_pending_tasks_for(source) assert_raise Ecto.NoResultsError, fn -> Tasks.get_task!(task.id) end end test "it does not delete non-pending tasks" do - channel = channel_fixture() - task = Repo.preload(task_fixture(channel_id: channel.id), :job) + source = source_fixture() + task = Repo.preload(task_fixture(source_id: source.id), :job) :ok = Oban.cancel_job(task.job) - assert :ok = Tasks.delete_pending_tasks_for(channel) + assert :ok = Tasks.delete_pending_tasks_for(source) assert Tasks.get_task!(task.id) end diff --git a/test/pinchflat/workers/media_indexing_worker_test.exs b/test/pinchflat/workers/media_indexing_worker_test.exs index dd044c5e..7a9a87bf 100644 --- a/test/pinchflat/workers/media_indexing_worker_test.exs +++ b/test/pinchflat/workers/media_indexing_worker_test.exs @@ -12,36 +12,36 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do setup :verify_on_exit! describe "perform/1" do - test "it does not do any indexing if the channel shouldn't be indexed" do + test "it does not do any indexing if the source shouldn't be indexed" do expect(YtDlpRunnerMock, :run, 0, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: -1) + source = source_fixture(index_frequency_minutes: -1) - perform_job(MediaIndexingWorker, %{id: channel.id}) + perform_job(MediaIndexingWorker, %{id: source.id}) end - test "it does not reschedule if the channel shouldn't be indexed" do + test "it does not reschedule if the source shouldn't be indexed" do expect(YtDlpRunnerMock, :run, 0, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: -1) - perform_job(MediaIndexingWorker, %{id: channel.id}) + source = source_fixture(index_frequency_minutes: -1) + perform_job(MediaIndexingWorker, %{id: source.id}) - refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) + refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id}) end - test "it indexes the channel if it should be indexed" do + test "it indexes the source if it should be indexed" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) - perform_job(MediaIndexingWorker, %{id: channel.id}) + perform_job(MediaIndexingWorker, %{id: source.id}) end test "it kicks off a download job for each pending media item" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1"} end) - channel = channel_fixture(index_frequency_minutes: 10) - perform_job(MediaIndexingWorker, %{id: channel.id}) + source = source_fixture(index_frequency_minutes: 10) + perform_job(MediaIndexingWorker, %{id: source.id}) assert [_] = all_enqueued(worker: VideoDownloadWorker) end @@ -49,9 +49,9 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it starts a job for any pending media item even if it's from another run" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1"} end) - channel = channel_fixture(index_frequency_minutes: 10) - media_item_fixture(%{channel_id: channel.id, media_filepath: nil}) - perform_job(MediaIndexingWorker, %{id: channel.id}) + source = source_fixture(index_frequency_minutes: 10) + media_item_fixture(%{source_id: source.id, media_filepath: nil}) + perform_job(MediaIndexingWorker, %{id: source.id}) assert [_, _] = all_enqueued(worker: VideoDownloadWorker) end @@ -59,8 +59,8 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it does not kick off a job for media items that could not be saved" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1\nvideo1"} end) - channel = channel_fixture(index_frequency_minutes: 10) - perform_job(MediaIndexingWorker, %{id: channel.id}) + source = source_fixture(index_frequency_minutes: 10) + perform_job(MediaIndexingWorker, %{id: source.id}) # Only one job should be enqueued, since the second video is a duplicate assert [_] = all_enqueued(worker: VideoDownloadWorker) @@ -69,41 +69,41 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it reschedules the job based on the index frequency" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: 10) - perform_job(MediaIndexingWorker, %{id: channel.id}) + source = source_fixture(index_frequency_minutes: 10) + perform_job(MediaIndexingWorker, %{id: source.id}) assert_enqueued( worker: MediaIndexingWorker, - args: %{"id" => channel.id}, - scheduled_at: now_plus(channel.index_frequency_minutes, :minutes) + args: %{"id" => source.id}, + scheduled_at: now_plus(source.index_frequency_minutes, :minutes) ) end test "it creates a task for the rescheduled job" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) task_count_fetcher = fn -> Enum.count(Tasks.list_tasks()) end assert_changed([from: 0, to: 1], task_count_fetcher, fn -> - perform_job(MediaIndexingWorker, %{id: channel.id}) + perform_job(MediaIndexingWorker, %{id: source.id}) end) end test "it creates the basic media_item records" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1\nvideo2"} end) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) media_item_fetcher = fn -> - channel + source |> Repo.preload(:media_items) |> Map.get(:media_items) |> Enum.map(fn media_item -> media_item.media_id end) end assert_changed([from: [], to: ["video1", "video2"]], media_item_fetcher, fn -> - perform_job(MediaIndexingWorker, %{id: channel.id}) + perform_job(MediaIndexingWorker, %{id: source.id}) end) end end diff --git a/test/pinchflat/workers/video_download_worker_test.exs b/test/pinchflat/workers/video_download_worker_test.exs index da4cb66b..3a515f9b 100644 --- a/test/pinchflat/workers/video_download_worker_test.exs +++ b/test/pinchflat/workers/video_download_worker_test.exs @@ -12,7 +12,7 @@ defmodule Pinchflat.Workers.VideoDownloadWorkerTest do media_item = Repo.preload( media_item_fixture(%{media_filepath: nil}), - [:metadata, channel: :media_profile] + [:metadata, source: :media_profile] ) {:ok, %{media_item: media_item}} diff --git a/test/pinchflat_web/controllers/channel_controller_test.exs b/test/pinchflat_web/controllers/channel_controller_test.exs deleted file mode 100644 index 5e71f11f..00000000 --- a/test/pinchflat_web/controllers/channel_controller_test.exs +++ /dev/null @@ -1,119 +0,0 @@ -defmodule PinchflatWeb.ChannelControllerTest do - use PinchflatWeb.ConnCase - import Mox - - import Pinchflat.ProfilesFixtures - import Pinchflat.MediaSourceFixtures - - setup do - media_profile = media_profile_fixture() - - { - :ok, - %{ - create_attrs: %{ - media_profile_id: media_profile.id, - original_url: "https://www.youtube.com/channel/abc123" - }, - update_attrs: %{ - original_url: "https://www.youtube.com/channel/321xyz" - }, - invalid_attrs: %{original_url: nil, media_profile_id: nil} - } - } - end - - setup :verify_on_exit! - - describe "index" do - test "lists all channels", %{conn: conn} do - conn = get(conn, ~p"/media_sources/channels") - assert html_response(conn, 200) =~ "Listing Channels" - end - end - - describe "new channel" do - test "renders form", %{conn: conn} do - conn = get(conn, ~p"/media_sources/channels/new") - assert html_response(conn, 200) =~ "New Channel" - end - end - - describe "create channel" do - test "redirects to show when data is valid", %{conn: conn, create_attrs: create_attrs} do - expect(YtDlpRunnerMock, :run, 1, &runner_function_mock/3) - conn = post(conn, ~p"/media_sources/channels", channel: create_attrs) - - assert %{id: id} = redirected_params(conn) - assert redirected_to(conn) == ~p"/media_sources/channels/#{id}" - - conn = get(conn, ~p"/media_sources/channels/#{id}") - assert html_response(conn, 200) =~ "Channel #{id}" - end - - test "renders errors when data is invalid", %{conn: conn, invalid_attrs: invalid_attrs} do - conn = post(conn, ~p"/media_sources/channels", channel: invalid_attrs) - assert html_response(conn, 200) =~ "New Channel" - end - end - - describe "edit channel" do - setup [:create_channel] - - test "renders form for editing chosen channel", %{conn: conn, channel: channel} do - conn = get(conn, ~p"/media_sources/channels/#{channel}/edit") - assert html_response(conn, 200) =~ "Edit Channel" - end - end - - describe "update channel" do - setup [:create_channel] - - test "redirects when data is valid", %{conn: conn, channel: channel, update_attrs: update_attrs} do - expect(YtDlpRunnerMock, :run, 1, &runner_function_mock/3) - - conn = put(conn, ~p"/media_sources/channels/#{channel}", channel: update_attrs) - assert redirected_to(conn) == ~p"/media_sources/channels/#{channel}" - - conn = get(conn, ~p"/media_sources/channels/#{channel}") - assert html_response(conn, 200) =~ "https://www.youtube.com/channel/321xyz" - end - - test "renders errors when data is invalid", %{ - conn: conn, - channel: channel, - invalid_attrs: invalid_attrs - } do - conn = put(conn, ~p"/media_sources/channels/#{channel}", channel: invalid_attrs) - assert html_response(conn, 200) =~ "Edit Channel" - end - end - - describe "delete channel" do - setup [:create_channel] - - test "deletes chosen channel", %{conn: conn, channel: channel} do - conn = delete(conn, ~p"/media_sources/channels/#{channel}") - assert redirected_to(conn) == ~p"/media_sources/channels" - - assert_error_sent 404, fn -> - get(conn, ~p"/media_sources/channels/#{channel}") - end - end - end - - defp create_channel(_) do - channel = channel_fixture() - %{channel: channel} - end - - defp runner_function_mock(_url, _opts, _ot) do - { - :ok, - Phoenix.json_library().encode!(%{ - channel: "some name", - channel_id: "some_channel_id_#{:rand.uniform(1_000_000)}" - }) - } - end -end diff --git a/test/pinchflat_web/controllers/source_controller_test.exs b/test/pinchflat_web/controllers/source_controller_test.exs new file mode 100644 index 00000000..f973e8d9 --- /dev/null +++ b/test/pinchflat_web/controllers/source_controller_test.exs @@ -0,0 +1,120 @@ +defmodule PinchflatWeb.SourceControllerTest do + use PinchflatWeb.ConnCase + import Mox + + import Pinchflat.ProfilesFixtures + import Pinchflat.MediaSourceFixtures + + setup do + media_profile = media_profile_fixture() + + { + :ok, + %{ + create_attrs: %{ + media_profile_id: media_profile.id, + collection_type: "channel", + original_url: "https://www.youtube.com/source/abc123" + }, + update_attrs: %{ + original_url: "https://www.youtube.com/source/321xyz" + }, + invalid_attrs: %{original_url: nil, media_profile_id: nil} + } + } + end + + setup :verify_on_exit! + + describe "index" do + test "lists all sources", %{conn: conn} do + conn = get(conn, ~p"/media_sources/sources") + assert html_response(conn, 200) =~ "Listing Sources" + end + end + + describe "new source" do + test "renders form", %{conn: conn} do + conn = get(conn, ~p"/media_sources/sources/new") + assert html_response(conn, 200) =~ "New Source" + end + end + + describe "create source" do + test "redirects to show when data is valid", %{conn: conn, create_attrs: create_attrs} do + expect(YtDlpRunnerMock, :run, 1, &runner_function_mock/3) + conn = post(conn, ~p"/media_sources/sources", source: create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == ~p"/media_sources/sources/#{id}" + + conn = get(conn, ~p"/media_sources/sources/#{id}") + assert html_response(conn, 200) =~ "Source #{id}" + end + + test "renders errors when data is invalid", %{conn: conn, invalid_attrs: invalid_attrs} do + conn = post(conn, ~p"/media_sources/sources", source: invalid_attrs) + assert html_response(conn, 200) =~ "New Source" + end + end + + describe "edit source" do + setup [:create_source] + + test "renders form for editing chosen source", %{conn: conn, source: source} do + conn = get(conn, ~p"/media_sources/sources/#{source}/edit") + assert html_response(conn, 200) =~ "Edit Source" + end + end + + describe "update source" do + setup [:create_source] + + test "redirects when data is valid", %{conn: conn, source: source, update_attrs: update_attrs} do + expect(YtDlpRunnerMock, :run, 1, &runner_function_mock/3) + + conn = put(conn, ~p"/media_sources/sources/#{source}", source: update_attrs) + assert redirected_to(conn) == ~p"/media_sources/sources/#{source}" + + conn = get(conn, ~p"/media_sources/sources/#{source}") + assert html_response(conn, 200) =~ "https://www.youtube.com/source/321xyz" + end + + test "renders errors when data is invalid", %{ + conn: conn, + source: source, + invalid_attrs: invalid_attrs + } do + conn = put(conn, ~p"/media_sources/sources/#{source}", source: invalid_attrs) + assert html_response(conn, 200) =~ "Edit Source" + end + end + + describe "delete source" do + setup [:create_source] + + test "deletes chosen source", %{conn: conn, source: source} do + conn = delete(conn, ~p"/media_sources/sources/#{source}") + assert redirected_to(conn) == ~p"/media_sources/sources" + + assert_error_sent 404, fn -> + get(conn, ~p"/media_sources/sources/#{source}") + end + end + end + + defp create_source(_) do + source = source_fixture() + %{source: source} + end + + defp runner_function_mock(_url, _opts, _ot) do + { + :ok, + Phoenix.json_library().encode!(%{ + channel: "some name", + channel_id: "some_source_id_#{:rand.uniform(1_000_000)}" + }) + } + end +end diff --git a/test/support/fixtures/media_fixtures.ex b/test/support/fixtures/media_fixtures.ex index 6d0d727f..5f75cc48 100644 --- a/test/support/fixtures/media_fixtures.ex +++ b/test/support/fixtures/media_fixtures.ex @@ -16,7 +16,7 @@ defmodule Pinchflat.MediaFixtures do media_id: Faker.String.base64(12), title: Faker.Commerce.product_name(), media_filepath: "/video/#{Faker.File.file_name(:video)}", - channel_id: MediaSourceFixtures.channel_fixture().id + source_id: MediaSourceFixtures.source_fixture().id }) |> Pinchflat.Media.create_media_item() diff --git a/test/support/fixtures/media_source_fixtures.ex b/test/support/fixtures/media_source_fixtures.ex index 007c9d4a..178ccc99 100644 --- a/test/support/fixtures/media_source_fixtures.ex +++ b/test/support/fixtures/media_source_fixtures.ex @@ -6,18 +6,19 @@ defmodule Pinchflat.MediaSourceFixtures do alias Pinchflat.Repo alias Pinchflat.ProfilesFixtures - alias Pinchflat.MediaSource.Channel + alias Pinchflat.MediaSource.Source @doc """ - Generate a channel. + Generate a source. """ - def channel_fixture(attrs \\ %{}) do - {:ok, channel} = - %Channel{} - |> Channel.changeset( + def source_fixture(attrs \\ %{}) do + {:ok, source} = + %Source{} + |> Source.changeset( Enum.into(attrs, %{ - name: "Channel ##{:rand.uniform(1_000_000)}", - channel_id: Base.encode16(:crypto.hash(:md5, "#{:rand.uniform(1_000_000)}")), + name: "Source ##{:rand.uniform(1_000_000)}", + collection_id: Base.encode16(:crypto.hash(:md5, "#{:rand.uniform(1_000_000)}")), + collection_type: "channel", original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}", media_profile_id: ProfilesFixtures.media_profile_fixture().id, index_frequency_minutes: 60 @@ -25,6 +26,6 @@ defmodule Pinchflat.MediaSourceFixtures do ) |> Repo.insert() - channel + source end end diff --git a/test/support/fixtures/tasks_fixtures.ex b/test/support/fixtures/tasks_fixtures.ex index c310ea60..7bb11217 100644 --- a/test/support/fixtures/tasks_fixtures.ex +++ b/test/support/fixtures/tasks_fixtures.ex @@ -14,7 +14,7 @@ defmodule Pinchflat.TasksFixtures do {:ok, task} = attrs |> Enum.into(%{ - channel_id: MediaSourceFixtures.channel_fixture().id, + source_id: MediaSourceFixtures.source_fixture().id, job_id: JobFixtures.job_fixture().id }) |> Pinchflat.Tasks.create_task()