diff --git a/lib/music_library/assets.ex b/lib/music_library/assets.ex new file mode 100644 index 00000000..cfec2bbb --- /dev/null +++ b/lib/music_library/assets.ex @@ -0,0 +1,10 @@ +defmodule MusicLibrary.Assets do + alias MusicLibrary.Assets.Asset + alias MusicLibrary.Repo + + def store(params) do + %Asset{} + |> Asset.changeset(params) + |> Repo.insert() + end +end diff --git a/lib/music_library/assets/asset.ex b/lib/music_library/assets/asset.ex new file mode 100644 index 00000000..69ead037 --- /dev/null +++ b/lib/music_library/assets/asset.ex @@ -0,0 +1,36 @@ +defmodule MusicLibrary.Assets.Asset do + use Ecto.Schema + + import Ecto.Changeset + + @primary_key {:hash, :string, autogenerate: false} + schema "assets" do + field :content, :binary + field :format, :string + field :properties, :map, default: %{}, read_after_writes: true + + timestamps(type: :utc_datetime) + end + + def changeset(asset, attrs) do + asset + |> cast(attrs, [:content, :format, :properties]) + |> validate_required([:content, :format]) + |> generate_hash() + |> unique_constraint(:hash) + end + + defp generate_hash(changeset) do + case get_change(changeset, :content) do + nil -> + changeset + + content -> + put_change(changeset, :hash, hash(content)) + end + end + + defp hash(content) do + :crypto.hash(:sha256, content) |> Base.encode16() + end +end diff --git a/priv/repo/migrations/20250901072717_create_assets.exs b/priv/repo/migrations/20250901072717_create_assets.exs new file mode 100644 index 00000000..cfff0769 --- /dev/null +++ b/priv/repo/migrations/20250901072717_create_assets.exs @@ -0,0 +1,14 @@ +defmodule MusicLibrary.Repo.Migrations.CreateAssets do + use Ecto.Migration + + def change do + create table(:assets, primary_key: false) do + add :hash, :string, primary_key: true + add :content, :binary, null: false + add :format, :string, null: false + add :properties, :map, default: %{} + + timestamps(type: :utc_datetime) + end + end +end diff --git a/test/music_library/assets_test.exs b/test/music_library/assets_test.exs new file mode 100644 index 00000000..ac1efb63 --- /dev/null +++ b/test/music_library/assets_test.exs @@ -0,0 +1,34 @@ +defmodule MusicLibrary.AssetsTest do + use MusicLibrary.DataCase + + alias MusicLibrary.Assets + + describe "store/1" do + test "stores using the hash as key" do + params = %{ + content: "some content", + format: "text/plain", + properties: %{language: "english"} + } + + assert {:ok, asset} = Assets.store(params) + assert asset.hash == "290F493C44F5D63D06B374D0A5ABD292FAE38B92CAB2FAE5EFEFE1B0E9347F56" + assert asset.properties == %{"language" => "english"} + assert asset.content == "some content" + assert asset.format == "text/plain" + end + + test "prevents duplicates" do + params = %{ + content: "some content", + format: "text/plain", + properties: %{language: "english"} + } + + assert {:ok, _asset} = Assets.store(params) + assert {:error, changeset} = Assets.store(params) + + assert [hash: {"has already been taken", _}] = changeset.errors + end + end +end