First pass at record sets

This commit is contained in:
Claudio Ortolina
2026-02-05 11:46:52 +00:00
parent b17fa006d5
commit 419b093101
13 changed files with 1089 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
defmodule MusicLibrary.RecordSets do
import Ecto.Query, warn: false
alias MusicLibrary.RecordSets.{RecordSet, RecordSetItem}
alias MusicLibrary.Repo
def list_record_sets(opts \\ []) do
offset = Keyword.get(opts, :offset, 0)
limit = Keyword.get(opts, :limit, 20)
from(rs in RecordSet,
order_by: [desc: rs.updated_at],
offset: ^offset,
limit: ^limit,
preload: [items: :record]
)
|> Repo.all()
end
def count_record_sets do
Repo.aggregate(RecordSet, :count)
end
def get_record_set!(id) do
RecordSet
|> Repo.get!(id)
|> Repo.preload(items: :record)
end
def create_record_set(attrs) do
%RecordSet{}
|> RecordSet.changeset(attrs)
|> Repo.insert()
|> case do
{:ok, record_set} -> {:ok, Repo.preload(record_set, items: :record)}
error -> error
end
end
def update_record_set(%RecordSet{} = record_set, attrs) do
record_set
|> RecordSet.changeset(attrs)
|> Repo.update()
|> case do
{:ok, record_set} -> {:ok, Repo.preload(record_set, [items: :record], force: true)}
error -> error
end
end
def delete_record_set(%RecordSet{} = record_set) do
Repo.delete(record_set)
end
def change_record_set(%RecordSet{} = record_set, attrs \\ %{}) do
RecordSet.changeset(record_set, attrs)
end
def add_record_to_set(%RecordSet{} = record_set, record_id) do
next_position =
from(i in RecordSetItem,
where: i.record_set_id == ^record_set.id,
select: coalesce(max(i.position), -1)
)
|> Repo.one!()
|> Kernel.+(1)
%RecordSetItem{}
|> RecordSetItem.changeset(%{position: next_position})
|> Ecto.Changeset.put_change(:record_set_id, record_set.id)
|> Ecto.Changeset.put_change(:record_id, record_id)
|> Ecto.Changeset.unique_constraint([:record_set_id, :record_id])
|> Repo.insert()
|> case do
{:ok, _item} -> {:ok, get_record_set!(record_set.id)}
error -> error
end
end
def remove_record_from_set(%RecordSet{} = record_set, record_id) do
item =
from(i in RecordSetItem,
where: i.record_set_id == ^record_set.id and i.record_id == ^record_id
)
|> Repo.one!()
Repo.delete(item)
recompact_positions(record_set.id)
{:ok, get_record_set!(record_set.id)}
end
def move_record_in_set(%RecordSet{} = record_set, record_id, direction)
when direction in [:up, :down] do
items =
from(i in RecordSetItem,
where: i.record_set_id == ^record_set.id,
order_by: [asc: i.position]
)
|> Repo.all()
index = Enum.find_index(items, fn i -> i.record_id == record_id end)
swap_index =
case direction do
:up -> index - 1
:down -> index + 1
end
if index && swap_index >= 0 && swap_index < length(items) do
item_a = Enum.at(items, index)
item_b = Enum.at(items, swap_index)
Repo.transaction(fn ->
item_a
|> Ecto.Changeset.change(position: item_b.position)
|> Repo.update!()
item_b
|> Ecto.Changeset.change(position: item_a.position)
|> Repo.update!()
end)
end
{:ok, get_record_set!(record_set.id)}
end
defp recompact_positions(record_set_id) do
items =
from(i in RecordSetItem,
where: i.record_set_id == ^record_set_id,
order_by: [asc: i.position]
)
|> Repo.all()
Enum.with_index(items, fn item, index ->
if item.position != index do
item
|> Ecto.Changeset.change(position: index)
|> Repo.update!()
end
end)
end
end
@@ -0,0 +1,24 @@
defmodule MusicLibrary.RecordSets.RecordSet do
use Ecto.Schema
import Ecto.Changeset
alias MusicLibrary.RecordSets.RecordSetItem
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "record_sets" do
field :name, :string
field :description, :string
has_many :items, RecordSetItem, preload_order: [asc: :position]
timestamps(type: :utc_datetime)
end
def changeset(record_set, attrs) do
record_set
|> cast(attrs, [:name, :description])
|> validate_required([:name])
end
end
@@ -0,0 +1,25 @@
defmodule MusicLibrary.RecordSets.RecordSetItem do
use Ecto.Schema
import Ecto.Changeset
alias MusicLibrary.Records.Record
alias MusicLibrary.RecordSets.RecordSet
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "record_set_items" do
field :position, :integer
belongs_to :record_set, RecordSet
belongs_to :record, Record
timestamps(type: :utc_datetime)
end
def changeset(record_set_item, attrs) do
record_set_item
|> cast(attrs, [:position])
|> validate_required([:position])
end
end