diff --git a/assets/yarn.lock b/assets/yarn.lock new file mode 100644 index 0000000..fb57ccd --- /dev/null +++ b/assets/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/lib/pinchflat/media.ex b/lib/pinchflat/media.ex new file mode 100644 index 0000000..b8cb20a --- /dev/null +++ b/lib/pinchflat/media.ex @@ -0,0 +1,56 @@ +defmodule Pinchflat.Media do + @moduledoc """ + The Media context. + """ + + import Ecto.Query, warn: false + alias Pinchflat.Repo + + alias Pinchflat.Media.MediaItem + + @doc """ + Returns the list of media_items. Returns [%MediaItem{}, ...]. + """ + def list_media_items do + Repo.all(MediaItem) + end + + @doc """ + Gets a single media_item. + + Returns %MediaItem{}. Raises `Ecto.NoResultsError` if the Media item does not exist. + """ + def get_media_item!(id), do: Repo.get!(MediaItem, id) + + @doc """ + Creates a media_item. Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}. + """ + def create_media_item(attrs \\ %{}) do + %MediaItem{} + |> MediaItem.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a media_item. Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}. + """ + def update_media_item(%MediaItem{} = media_item, attrs) do + media_item + |> MediaItem.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a media_item. Returns {:ok, %MediaItem{}} | {:error, %Ecto.Changeset{}}. + """ + def delete_media_item(%MediaItem{} = media_item) do + Repo.delete(media_item) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking media_item changes. + """ + def change_media_item(%MediaItem{} = media_item, attrs \\ %{}) do + MediaItem.changeset(media_item, attrs) + end +end diff --git a/lib/pinchflat/media/media_item.ex b/lib/pinchflat/media/media_item.ex new file mode 100644 index 0000000..0f19bf2 --- /dev/null +++ b/lib/pinchflat/media/media_item.ex @@ -0,0 +1,26 @@ +defmodule Pinchflat.Media.MediaItem do + use Ecto.Schema + import Ecto.Changeset + + alias Pinchflat.MediaSource.Channel + + @required_fields ~w(media_id channel_id)a + @allowed_fields ~w(title media_id video_filepath channel_id)a + + schema "media_items" do + field :title, :string + field :media_id, :string + field :video_filepath, :string + + belongs_to :channel, Channel + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(media_item, attrs) do + media_item + |> cast(attrs, @allowed_fields) + |> validate_required(@required_fields) + end +end diff --git a/lib/pinchflat/media_source/channel.ex b/lib/pinchflat/media_source/channel.ex index 4861f65..3e77496 100644 --- a/lib/pinchflat/media_source/channel.ex +++ b/lib/pinchflat/media_source/channel.ex @@ -6,6 +6,7 @@ defmodule Pinchflat.MediaSource.Channel do use Ecto.Schema import Ecto.Changeset + alias Pinchflat.Media.MediaItem alias Pinchflat.Profiles.MediaProfile @required_fields ~w(name channel_id original_url media_profile_id)a @@ -20,6 +21,8 @@ defmodule Pinchflat.MediaSource.Channel do belongs_to :media_profile, MediaProfile + has_many :media_items, MediaItem + timestamps(type: :utc_datetime) end diff --git a/mix.exs b/mix.exs index cc91b60..d0d9065 100644 --- a/mix.exs +++ b/mix.exs @@ -53,7 +53,8 @@ defmodule Pinchflat.MixProject do {:plug_cowboy, "~> 2.5"}, {:mox, "~> 1.0", only: :test}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, - {:nimble_parsec, "~> 1.4"} + {:nimble_parsec, "~> 1.4"}, + {:faker, "~> 0.17", only: :test} ] end diff --git a/mix.lock b/mix.lock index 2ea91e9..4824481 100644 --- a/mix.lock +++ b/mix.lock @@ -12,6 +12,7 @@ "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"}, + "faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.17.0", "17d06e1d44d891d20dbd437335eebe844e2426a0cd7e3a3e220b461127c73f70", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8d014a661bb6a437263d4b5abf0bcbd3cf0deb26b1e8596f2a271d22e48934c7"}, "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, diff --git a/priv/repo/migrations/20240125025325_create_media_items.exs b/priv/repo/migrations/20240125025325_create_media_items.exs new file mode 100644 index 0000000..c02e6e7 --- /dev/null +++ b/priv/repo/migrations/20240125025325_create_media_items.exs @@ -0,0 +1,17 @@ +defmodule Pinchflat.Repo.Migrations.CreateMediaItems do + use Ecto.Migration + + def change do + create table(:media_items) do + add :media_id, :string, null: false + add :title, :string + add :video_filepath, :string + add :channel_id, references(:channels, on_delete: :restrict), null: false + + timestamps(type: :utc_datetime) + end + + create index(:media_items, [:channel_id]) + create unique_index(:media_items, [:channel_id, :media_id]) + end +end diff --git a/test/pinchflat/media_test.exs b/test/pinchflat/media_test.exs new file mode 100644 index 0000000..405ad4d --- /dev/null +++ b/test/pinchflat/media_test.exs @@ -0,0 +1,84 @@ +defmodule Pinchflat.MediaTest do + use Pinchflat.DataCase + + import Pinchflat.MediaFixtures + import Pinchflat.MediaSourceFixtures + + alias Pinchflat.Media + alias Pinchflat.Media.MediaItem + + @invalid_attrs %{title: nil, media_id: nil, video_filepath: nil} + + describe "list_media_items/0" do + test "it returns all media_items" do + media_item = media_item_fixture() + assert Media.list_media_items() == [media_item] + end + end + + describe "get_media_item!/1" do + test "it returns the media_item with given id" do + media_item = media_item_fixture() + assert Media.get_media_item!(media_item.id) == media_item + end + end + + describe "create_media_item/1" do + test "creating with valid data creates a media_item" do + valid_attrs = %{ + media_id: Faker.String.base64(12), + title: Faker.Commerce.product_name(), + video_filepath: "/video/#{Faker.File.file_name(:video)}", + channel_id: channel_fixture().id + } + + assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs) + assert media_item.title == valid_attrs.title + assert media_item.media_id == valid_attrs.media_id + assert media_item.video_filepath == valid_attrs.video_filepath + end + + test "creating with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Media.create_media_item(@invalid_attrs) + end + end + + describe "update_media_item/2" do + test "updating with valid data updates the media_item" do + media_item = media_item_fixture() + + update_attrs = %{ + media_id: Faker.String.base64(12), + title: Faker.Commerce.product_name(), + video_filepath: "/video/#{Faker.File.file_name(:video)}", + channel_id: channel_fixture().id + } + + assert {:ok, %MediaItem{} = media_item} = Media.update_media_item(media_item, update_attrs) + assert media_item.title == update_attrs.title + assert media_item.media_id == update_attrs.media_id + assert media_item.video_filepath == update_attrs.video_filepath + end + + test "updating with invalid data returns error changeset" do + media_item = media_item_fixture() + assert {:error, %Ecto.Changeset{}} = Media.update_media_item(media_item, @invalid_attrs) + assert media_item == Media.get_media_item!(media_item.id) + end + end + + describe "delete_media_item/1" do + test "deletion deletes the media_item" do + media_item = media_item_fixture() + assert {:ok, %MediaItem{}} = Media.delete_media_item(media_item) + assert_raise Ecto.NoResultsError, fn -> Media.get_media_item!(media_item.id) end + end + end + + describe "change_media_item/1" do + test "change_media_item/1 returns a media_item changeset" do + media_item = media_item_fixture() + assert %Ecto.Changeset{} = Media.change_media_item(media_item) + end + end +end diff --git a/test/pinchflat/profiles_test.exs b/test/pinchflat/profiles_test.exs index 9f54b39..8635750 100644 --- a/test/pinchflat/profiles_test.exs +++ b/test/pinchflat/profiles_test.exs @@ -2,25 +2,27 @@ defmodule Pinchflat.ProfilesTest do use Pinchflat.DataCase alias Pinchflat.Profiles + alias Pinchflat.Profiles.MediaProfile + import Pinchflat.ProfilesFixtures - describe "media_profiles" do - alias Pinchflat.Profiles.MediaProfile + @invalid_attrs %{name: nil, output_path_template: nil} - import Pinchflat.ProfilesFixtures - - @invalid_attrs %{name: nil, output_path_template: nil} - - test "list_media_profiles/0 returns all media_profiles" do + describe "list_media_profiles/0" do + test "it returns all media_profiles" do media_profile = media_profile_fixture() assert Profiles.list_media_profiles() == [media_profile] end + end - test "get_media_profile!/1 returns the media_profile with given id" do + describe "get_media_profile!/1" do + test "it returns the media_profile with given id" do media_profile = media_profile_fixture() assert Profiles.get_media_profile!(media_profile.id) == media_profile end + end - test "create_media_profile/1 with valid data creates a media_profile" do + describe "create_media_profile/1" do + test "creation with valid data creates a media_profile" do valid_attrs = %{name: "some name", output_path_template: "some output_path_template"} assert {:ok, %MediaProfile{} = media_profile} = Profiles.create_media_profile(valid_attrs) @@ -28,11 +30,13 @@ defmodule Pinchflat.ProfilesTest do assert media_profile.output_path_template == "some output_path_template" end - test "create_media_profile/1 with invalid data returns error changeset" do + test "creation with invalid data returns error changeset" do assert {:error, %Ecto.Changeset{}} = Profiles.create_media_profile(@invalid_attrs) end + end - test "update_media_profile/2 with valid data updates the media_profile" do + describe "update_media_profile/2" do + test "updating with valid data updates the media_profile" do media_profile = media_profile_fixture() update_attrs = %{ @@ -47,7 +51,7 @@ defmodule Pinchflat.ProfilesTest do assert media_profile.output_path_template == "some updated output_path_template" end - test "update_media_profile/2 with invalid data returns error changeset" do + test "updating with invalid data returns error changeset" do media_profile = media_profile_fixture() assert {:error, %Ecto.Changeset{}} = @@ -55,14 +59,18 @@ defmodule Pinchflat.ProfilesTest do assert media_profile == Profiles.get_media_profile!(media_profile.id) end + end - test "delete_media_profile/1 deletes the media_profile" do + describe "delete_media_profile/1" do + test "deletion deletes the media_profile" do media_profile = media_profile_fixture() assert {:ok, %MediaProfile{}} = Profiles.delete_media_profile(media_profile) assert_raise Ecto.NoResultsError, fn -> Profiles.get_media_profile!(media_profile.id) end end + end - test "change_media_profile/1 returns a media_profile changeset" do + describe "change_media_profile/1" do + test "it returns a media_profile changeset" do media_profile = media_profile_fixture() assert %Ecto.Changeset{} = Profiles.change_media_profile(media_profile) end diff --git a/test/support/fixtures/media_fixtures.ex b/test/support/fixtures/media_fixtures.ex new file mode 100644 index 0000000..9db7014 --- /dev/null +++ b/test/support/fixtures/media_fixtures.ex @@ -0,0 +1,25 @@ +defmodule Pinchflat.MediaFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Pinchflat.Media` context. + """ + + alias Pinchflat.MediaSourceFixtures + + @doc """ + Generate a media_item. + """ + def media_item_fixture(attrs \\ %{}) do + {:ok, media_item} = + attrs + |> Enum.into(%{ + media_id: Faker.String.base64(12), + title: Faker.Commerce.product_name(), + video_filepath: "/video/#{Faker.File.file_name(:video)}", + channel_id: MediaSourceFixtures.channel_fixture().id + }) + |> Pinchflat.Media.create_media_item() + + media_item + end +end diff --git a/test/support/fixtures/media_source_fixtures.ex b/test/support/fixtures/media_source_fixtures.ex index 5be9bd3..271c32b 100644 --- a/test/support/fixtures/media_source_fixtures.ex +++ b/test/support/fixtures/media_source_fixtures.ex @@ -18,7 +18,7 @@ defmodule Pinchflat.MediaSourceFixtures do Enum.into(attrs, %{ name: "Channel ##{:rand.uniform(1_000_000)}", channel_id: Base.encode16(:crypto.hash(:md5, "#{:rand.uniform(1_000_000)}")), - original_url: "https://www.youtube.com/channel/#{:rand.uniform(1_000_000)}", + original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}", media_profile_id: ProfilesFixtures.media_profile_fixture().id }) ) diff --git a/test/test_helper.exs b/test/test_helper.exs index 54cb003..40d1202 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -3,3 +3,4 @@ Application.put_env(:pinchflat, :yt_dlp_runner, YtDlpRunnerMock) ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(Pinchflat.Repo, :manual) +Faker.start()