This commit is contained in:
Ondřej 2024-08-09 16:11:31 +02:00
parent c9791acff8
commit 8b17c987d8
4 changed files with 57 additions and 41 deletions

View file

@ -1,6 +1,7 @@
defmodule ChoreTracker.Accounts.User do defmodule ChoreTracker.Accounts.User do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias ChoreTracker.Chores
schema "users" do schema "users" do
field :username, :string field :username, :string
@ -12,6 +13,8 @@ defmodule ChoreTracker.Accounts.User do
field :display_name, :string field :display_name, :string
timestamps(type: :utc_datetime) timestamps(type: :utc_datetime)
has_many :assigned_chores, Chores.Chore, foreign_key: :next_assignee_id
end end
@doc """ @doc """

View file

@ -19,24 +19,41 @@ defmodule ChoreTracker.Chores do
do: Repo.get!(Chore, id) |> Repo.preload([{:logs, :user}, :assignees, :next_assignee]) do: Repo.get!(Chore, id) |> Repo.preload([{:logs, :user}, :assignees, :next_assignee])
def create_chore(attrs \\ %{}) do def create_chore(attrs \\ %{}) do
%Chore{} result =
|> change_chore(attrs) Ecto.Multi.new()
|> Repo.insert() |> Ecto.Multi.insert(:chore, change_chore(%Chore{}, attrs))
|> Ecto.Multi.update(:chore_with_assignee, fn %{chore: chore} ->
next_assignee = next_chore_assignee(chore)
change_chore_assignee(chore, next_assignee)
end)
|> Repo.transaction()
case result do
{:ok, %{chore_with_assignee: chore}} -> {:ok, chore}
{:error, :chore, changeset, _} -> {:error, changeset}
end
end end
def update_chore(%Chore{} = chore, attrs) do def update_chore(%Chore{} = chore, attrs) do
changeset = change_chore(chore, attrs) changeset = change_chore(chore, attrs)
Ecto.Multi.new() result =
|> Ecto.Multi.update(:chore, changeset) Ecto.Multi.new()
|> Ecto.Multi.run(:update_next_assignee, fn _repo, %{chore: chore} -> |> Ecto.Multi.update(:chore, changeset)
if Ecto.Changeset.changed?(changeset, :assignees) do |> Ecto.Multi.update(:chore_with_assignee, fn %{chore: chore} ->
save_next_chore_assignee(chore) if Ecto.Changeset.changed?(changeset, :assignees) do
else next_assignee = next_chore_assignee(chore)
{:ok, chore} change_chore_assignee(chore, next_assignee)
end else
end) Ecto.Changeset.change(chore)
|> Repo.transaction() end
end)
|> Repo.transaction()
case result do
{:ok, %{chore_with_assignee: chore}} -> {:ok, chore}
{:error, :chore, changeset, _} -> {:error, changeset}
end
end end
def delete_chore(%Chore{} = chore) do def delete_chore(%Chore{} = chore) do
@ -51,12 +68,14 @@ defmodule ChoreTracker.Chores do
|> Changeset.put_assoc(:assignees, assignees) |> Changeset.put_assoc(:assignees, assignees)
end end
def update_chore_assignee(%Chore{} = chore, attrs) do def change_chore_assignee(%Chore{} = chore, user) do
change_chore_assignee(chore, attrs) |> Repo.update() id =
end case user do
nil -> nil
%User{} -> user.id
end
def change_chore_assignee(%Chore{} = chore, attrs \\ %{}) do chore |> Ecto.Changeset.change(%{next_assignee_id: id})
chore |> Ecto.Changeset.cast(attrs, [:next_assignee_id])
end end
def get_last_chore_log_for_assignee(%Chore{} = chore, %User{} = user) do def get_last_chore_log_for_assignee(%Chore{} = chore, %User{} = user) do
@ -74,8 +93,9 @@ defmodule ChoreTracker.Chores do
:log, :log,
%ChoreLog{} |> ChoreLog.changeset(%{user_id: user.id, chore_id: chore.id}) %ChoreLog{} |> ChoreLog.changeset(%{user_id: user.id, chore_id: chore.id})
) )
|> Ecto.Multi.run(:chore, fn _repo, _change -> |> Ecto.Multi.update(:chore_assignee, fn _ ->
save_next_chore_assignee(chore) next_assignee = next_chore_assignee(chore)
change_chore_assignee(chore, next_assignee)
end) end)
|> Repo.transaction() |> Repo.transaction()
end end
@ -113,28 +133,21 @@ defmodule ChoreTracker.Chores do
Returns `nil` if chore has no assignees. Returns `nil` if chore has no assignees.
""" """
def next_chore_assignee(%Chore{} = chore) do def next_chore_assignee(%Chore{} = chore) do
chore = chore |> Repo.preload([{:assignees, :assigned_chores}])
chore.assignees chore.assignees
|> Enum.map(&{&1, get_last_chore_log_for_assignee(chore, &1)})
|> Enum.sort_by( |> Enum.sort_by(
fn {_assignee, log} -> fn assignee ->
case log do time =
nil -> 0 case get_last_chore_log_for_assignee(chore, assignee) do
%ChoreLog{inserted_at: inserted_at} -> DateTime.to_unix(inserted_at) nil -> 0
end %ChoreLog{inserted_at: inserted_at} -> DateTime.to_unix(inserted_at)
end
{time, length(assignee.assigned_chores)}
end, end,
:asc :desc
) )
|> Enum.map(fn {assignee, _log} -> assignee end)
|> List.first() |> List.first()
end end
defp save_next_chore_assignee(%Chore{} = chore) do
next_assignee = next_chore_assignee(chore)
if next_assignee do
chore |> update_chore_assignee(%{next_assignee_id: next_assignee.id})
else
{:ok, nil}
end
end
end end

View file

@ -21,7 +21,7 @@ defmodule ChoreTrackerWeb.ChoreLive.FormComponent do
> >
<div class="flex gap-2 flex-wrap"> <div class="flex gap-2 flex-wrap">
<.input field={@form[:name]} type="text" label="Name" wrapper_class="flex-1" /> <.input field={@form[:name]} type="text" label="Name" wrapper_class="flex-1" />
<.input field={@form[:emoji]} type="text" label="Emoji" /> <.input field={@form[:emoji]} type="text" label="Emoji" min="1" max="1" />
</div> </div>
<.input field={@form[:description]} type="textarea" label="Description" /> <.input field={@form[:description]} type="textarea" label="Description" />
<div class="flex gap-6 flex-wrap"> <div class="flex gap-6 flex-wrap">

View file

@ -41,7 +41,7 @@ defmodule ChoreTrackerWeb.ChoreLive.Show do
%{"chore" => chore_params} = params %{"chore" => chore_params} = params
chore = socket.assigns.chore chore = socket.assigns.chore
case Chores.update_chore_assignee(chore, chore_params) do case Chores.update_chore(chore, chore_params) do
{:ok, chore} -> {:ok, chore} ->
{:noreply, {:noreply,
socket socket
@ -57,6 +57,6 @@ defmodule ChoreTrackerWeb.ChoreLive.Show do
defp update_state(socket, chore) do defp update_state(socket, chore) do
socket socket
|> assign(:chore, chore |> Repo.preload(:next_assignee)) |> assign(:chore, chore |> Repo.preload(:next_assignee))
|> assign(:assignee_form, to_form(Chores.change_chore_assignee(chore))) |> assign(:assignee_form, to_form(Chores.change_chore(chore)))
end end
end end