Files
cannery/lib/cannery/activity_log.ex

398 lines
11 KiB
Elixir
Raw Normal View History

2022-02-15 17:33:45 -05:00
defmodule Cannery.ActivityLog do
@moduledoc """
The ActivityLog context.
"""
import Ecto.Query, warn: false
2023-03-30 21:53:52 -04:00
alias Cannery.Ammo.{Pack, Type}
2023-03-30 20:43:30 -04:00
alias Cannery.{Accounts.User, ActivityLog.ShotRecord, Repo}
2023-03-23 22:07:25 -04:00
alias Ecto.{Multi, Queryable}
2022-02-15 17:33:45 -05:00
@doc """
2023-03-30 20:43:30 -04:00
Returns the list of shot_records.
2022-02-15 17:33:45 -05:00
## Examples
2023-03-30 20:43:30 -04:00
iex> list_shot_records(:all, %User{id: 123})
[%ShotRecord{}, ...]
2022-02-15 17:33:45 -05:00
2023-03-30 20:43:30 -04:00
iex> list_shot_records("cool", :all, %User{id: 123})
[%ShotRecord{notes: "My cool shot record"}, ...]
2022-12-03 21:27:39 -05:00
2023-03-30 20:43:30 -04:00
iex> list_shot_records("cool", :rifle, %User{id: 123})
[%ShotRecord{notes: "Shot some rifle rounds"}, ...]
2023-03-23 22:07:25 -04:00
2022-02-15 17:33:45 -05:00
"""
2023-03-30 21:53:52 -04:00
@spec list_shot_records(Type.class() | :all, User.t()) :: [ShotRecord.t()]
@spec list_shot_records(search :: nil | String.t(), Type.class() | :all, User.t()) ::
2023-03-30 20:43:30 -04:00
[ShotRecord.t()]
def list_shot_records(search \\ nil, type, %User{id: user_id}) do
2023-06-05 18:35:19 -04:00
from(sr in ShotRecord,
as: :sr,
2023-05-12 21:48:04 -04:00
left_join: p in Pack,
as: :p,
2023-06-05 18:35:19 -04:00
on: sr.pack_id == p.id,
2023-03-30 21:53:52 -04:00
left_join: at in Type,
2023-03-23 22:07:25 -04:00
as: :at,
2023-05-12 21:48:04 -04:00
on: p.type_id == at.id,
2023-06-05 18:35:19 -04:00
where: sr.user_id == ^user_id,
distinct: sr.id
2023-03-23 22:07:25 -04:00
)
2023-03-30 20:43:30 -04:00
|> list_shot_records_search(search)
|> list_shot_records_filter_type(type)
2023-03-23 22:07:25 -04:00
|> Repo.all()
end
2022-12-03 21:27:39 -05:00
2023-03-30 20:43:30 -04:00
@spec list_shot_records_search(Queryable.t(), search :: String.t() | nil) ::
2023-03-23 22:07:25 -04:00
Queryable.t()
2023-03-30 20:43:30 -04:00
defp list_shot_records_search(query, search) when search in ["", nil], do: query
2022-12-03 21:27:39 -05:00
2023-03-30 20:43:30 -04:00
defp list_shot_records_search(query, search) when search |> is_binary() do
2022-12-03 21:27:39 -05:00
trimmed_search = String.trim(search)
2023-03-23 22:07:25 -04:00
query
|> where(
2023-06-05 18:35:19 -04:00
[sr: sr, p: p, at: at],
2023-03-23 22:07:25 -04:00
fragment(
"? @@ websearch_to_tsquery('english', ?)",
2023-06-05 18:35:19 -04:00
sr.search,
2023-03-23 22:07:25 -04:00
^trimmed_search
) or
fragment(
"? @@ websearch_to_tsquery('english', ?)",
2023-05-12 21:48:04 -04:00
p.search,
2023-03-23 22:07:25 -04:00
^trimmed_search
) or
fragment(
"? @@ websearch_to_tsquery('english', ?)",
at.search,
^trimmed_search
)
2022-12-03 21:27:39 -05:00
)
2023-06-05 18:35:19 -04:00
|> order_by([sr: sr], {
2023-03-23 22:07:25 -04:00
:desc,
fragment(
"ts_rank_cd(?, websearch_to_tsquery('english', ?), 4)",
2023-06-05 18:35:19 -04:00
sr.search,
2023-03-23 22:07:25 -04:00
^trimmed_search
)
})
2022-02-15 17:33:45 -05:00
end
2023-03-30 21:53:52 -04:00
@spec list_shot_records_filter_type(Queryable.t(), Type.class() | :all) ::
2023-03-23 22:07:25 -04:00
Queryable.t()
2023-03-30 20:43:30 -04:00
defp list_shot_records_filter_type(query, :rifle),
2023-03-28 23:08:40 -04:00
do: query |> where([at: at], at.class == :rifle)
2023-03-23 22:07:25 -04:00
2023-03-30 20:43:30 -04:00
defp list_shot_records_filter_type(query, :pistol),
2023-03-28 23:08:40 -04:00
do: query |> where([at: at], at.class == :pistol)
2023-03-23 22:07:25 -04:00
2023-03-30 20:43:30 -04:00
defp list_shot_records_filter_type(query, :shotgun),
2023-03-28 23:08:40 -04:00
do: query |> where([at: at], at.class == :shotgun)
2023-03-23 22:07:25 -04:00
2023-03-30 20:43:30 -04:00
defp list_shot_records_filter_type(query, _all), do: query
2023-03-23 22:07:25 -04:00
@doc """
Returns a count of shot records.
## Examples
iex> get_shot_record_count!(%User{id: 123})
3
"""
@spec get_shot_record_count!(User.t()) :: integer()
def get_shot_record_count!(%User{id: user_id}) do
Repo.one(
2023-06-05 18:35:19 -04:00
from sr in ShotRecord,
where: sr.user_id == ^user_id,
select: count(sr.id),
distinct: true
) || 0
end
2023-03-30 20:43:30 -04:00
@spec list_shot_records_for_pack(Pack.t(), User.t()) :: [ShotRecord.t()]
def list_shot_records_for_pack(
2023-03-29 22:54:55 -04:00
%Pack{id: pack_id, user_id: user_id},
%User{id: user_id}
) do
Repo.all(
2023-06-05 18:35:19 -04:00
from sr in ShotRecord,
where: sr.pack_id == ^pack_id,
where: sr.user_id == ^user_id
)
end
2022-02-15 17:33:45 -05:00
@doc """
2023-03-30 20:43:30 -04:00
Gets a single shot_record.
2022-02-15 17:33:45 -05:00
2023-03-30 20:43:30 -04:00
Raises `Ecto.NoResultsError` if the shot record does not exist.
2022-02-15 17:33:45 -05:00
## Examples
2023-03-30 20:43:30 -04:00
iex> get_shot_record!(123, %User{id: 123})
%ShotRecord{}
2022-02-15 17:33:45 -05:00
2023-03-30 20:43:30 -04:00
iex> get_shot_record!(456, %User{id: 123})
2022-02-15 17:33:45 -05:00
** (Ecto.NoResultsError)
"""
2023-03-30 20:43:30 -04:00
@spec get_shot_record!(ShotRecord.id(), User.t()) :: ShotRecord.t()
def get_shot_record!(id, %User{id: user_id}) do
2022-02-15 17:33:45 -05:00
Repo.one!(
2023-06-05 18:35:19 -04:00
from sr in ShotRecord,
where: sr.id == ^id,
where: sr.user_id == ^user_id,
order_by: sr.date
2022-02-15 17:33:45 -05:00
)
end
@doc """
2023-03-30 20:43:30 -04:00
Creates a shot_record.
2022-02-15 17:33:45 -05:00
## Examples
2023-03-30 20:43:30 -04:00
iex> create_shot_record(%{field: value}, %User{id: 123})
{:ok, %ShotRecord{}}
2022-02-15 17:33:45 -05:00
2023-03-30 20:43:30 -04:00
iex> create_shot_record(%{field: bad_value}, %User{id: 123})
2022-02-15 17:33:45 -05:00
{:error, %Ecto.Changeset{}}
"""
2023-03-30 20:43:30 -04:00
@spec create_shot_record(attrs :: map(), User.t(), Pack.t()) ::
{:ok, ShotRecord.t()} | {:error, ShotRecord.changeset() | nil}
def create_shot_record(attrs, user, pack) do
2022-07-01 21:39:08 -04:00
Multi.new()
|> Multi.insert(
2023-03-30 20:43:30 -04:00
:create_shot_record,
%ShotRecord{} |> ShotRecord.create_changeset(user, pack, attrs)
2022-07-01 21:39:08 -04:00
)
|> Multi.run(
2023-03-29 22:54:55 -04:00
:pack,
2023-03-30 20:43:30 -04:00
fn _repo, %{create_shot_record: %{pack_id: pack_id, user_id: user_id}} ->
2023-03-29 22:54:55 -04:00
pack =
Repo.one(
2023-05-12 21:48:04 -04:00
from p in Pack,
where: p.id == ^pack_id,
where: p.user_id == ^user_id
)
2023-03-29 22:54:55 -04:00
{:ok, pack}
2022-07-01 21:39:08 -04:00
end
)
|> Multi.update(
2023-03-29 22:54:55 -04:00
:update_pack,
2023-03-30 20:43:30 -04:00
fn %{create_shot_record: %{count: shot_record_count}, pack: %{count: pack_count}} ->
pack |> Pack.range_changeset(%{"count" => pack_count - shot_record_count})
2022-02-15 17:33:45 -05:00
end
2022-07-01 21:39:08 -04:00
)
|> Repo.transaction()
|> case do
2023-03-30 20:43:30 -04:00
{:ok, %{create_shot_record: shot_record}} -> {:ok, shot_record}
{:error, :create_shot_record, changeset, _changes_so_far} -> {:error, changeset}
2022-07-01 21:39:08 -04:00
{:error, _other_transaction, _value, _changes_so_far} -> {:error, nil}
2022-02-15 17:33:45 -05:00
end
end
@doc """
2023-03-30 20:43:30 -04:00
Updates a shot_record.
2022-02-15 17:33:45 -05:00
## Examples
2023-03-30 20:43:30 -04:00
iex> update_shot_record(shot_record, %{field: new_value}, %User{id: 123})
{:ok, %ShotRecord{}}
2022-02-15 17:33:45 -05:00
2023-03-30 20:43:30 -04:00
iex> update_shot_record(shot_record, %{field: bad_value}, %User{id: 123})
2022-02-15 17:33:45 -05:00
{:error, %Ecto.Changeset{}}
"""
2023-03-30 20:43:30 -04:00
@spec update_shot_record(ShotRecord.t(), attrs :: map(), User.t()) ::
{:ok, ShotRecord.t()} | {:error, ShotRecord.changeset() | nil}
def update_shot_record(
%ShotRecord{count: count, user_id: user_id} = shot_record,
2022-02-15 17:33:45 -05:00
attrs,
%User{id: user_id} = user
) do
2022-07-01 21:39:08 -04:00
Multi.new()
|> Multi.update(
2023-03-30 20:43:30 -04:00
:update_shot_record,
shot_record |> ShotRecord.update_changeset(user, attrs)
2022-07-01 21:39:08 -04:00
)
|> Multi.run(
2023-03-29 22:54:55 -04:00
:pack,
2023-03-30 20:43:30 -04:00
fn repo, %{update_shot_record: %{pack_id: pack_id, user_id: user_id}} ->
2023-05-12 21:48:04 -04:00
{:ok, repo.one(from p in Pack, where: p.id == ^pack_id and p.user_id == ^user_id)}
2022-07-01 21:39:08 -04:00
end
)
|> Multi.update(
2023-03-29 22:54:55 -04:00
:update_pack,
2022-07-01 21:39:08 -04:00
fn %{
2023-03-30 20:43:30 -04:00
update_shot_record: %{count: new_count},
2023-03-29 22:54:55 -04:00
pack: %{count: pack_count} = pack
2022-07-01 21:39:08 -04:00
} ->
shot_diff_to_add = new_count - count
2023-03-29 22:54:55 -04:00
new_pack_count = pack_count - shot_diff_to_add
pack |> Pack.range_changeset(%{"count" => new_pack_count})
2022-07-01 21:39:08 -04:00
end
)
|> Repo.transaction()
|> case do
2023-03-30 20:43:30 -04:00
{:ok, %{update_shot_record: shot_record}} -> {:ok, shot_record}
{:error, :update_shot_record, changeset, _changes_so_far} -> {:error, changeset}
2022-07-01 21:39:08 -04:00
{:error, _other_transaction, _value, _changes_so_far} -> {:error, nil}
2022-02-15 17:33:45 -05:00
end
end
@doc """
2023-03-30 20:43:30 -04:00
Deletes a shot_record.
2022-02-15 17:33:45 -05:00
## Examples
2023-03-30 20:43:30 -04:00
iex> delete_shot_record(shot_record, %User{id: 123})
{:ok, %ShotRecord{}}
2022-02-15 17:33:45 -05:00
2023-03-30 20:43:30 -04:00
iex> delete_shot_record(shot_record, %User{id: 123})
2022-02-15 17:33:45 -05:00
{:error, %Ecto.Changeset{}}
"""
2023-03-30 20:43:30 -04:00
@spec delete_shot_record(ShotRecord.t(), User.t()) ::
{:ok, ShotRecord.t()} | {:error, ShotRecord.changeset()}
def delete_shot_record(
%ShotRecord{user_id: user_id} = shot_record,
2022-07-01 21:39:08 -04:00
%User{id: user_id}
2022-02-15 17:33:45 -05:00
) do
Multi.new()
2023-03-30 20:43:30 -04:00
|> Multi.delete(:delete_shot_record, shot_record)
2022-07-01 21:39:08 -04:00
|> Multi.run(
2023-03-29 22:54:55 -04:00
:pack,
2023-03-30 20:43:30 -04:00
fn repo, %{delete_shot_record: %{pack_id: pack_id, user_id: user_id}} ->
2023-05-12 21:48:04 -04:00
{:ok, repo.one(from p in Pack, where: p.id == ^pack_id and p.user_id == ^user_id)}
2022-07-01 21:39:08 -04:00
end
)
2022-02-15 17:33:45 -05:00
|> Multi.update(
2023-03-29 22:54:55 -04:00
:update_pack,
2022-07-01 21:39:08 -04:00
fn %{
2023-03-30 20:43:30 -04:00
delete_shot_record: %{count: count},
2023-03-29 22:54:55 -04:00
pack: %{count: pack_count} = pack
2022-07-01 21:39:08 -04:00
} ->
2023-03-29 22:54:55 -04:00
new_pack_count = pack_count + count
pack |> Pack.range_changeset(%{"count" => new_pack_count})
2022-07-01 21:39:08 -04:00
end
2022-02-15 17:33:45 -05:00
)
|> Repo.transaction()
|> case do
2023-03-30 20:43:30 -04:00
{:ok, %{delete_shot_record: shot_record}} -> {:ok, shot_record}
{:error, :delete_shot_record, changeset, _changes_so_far} -> {:error, changeset}
2022-02-15 17:33:45 -05:00
{:error, _other_transaction, _value, _changes_so_far} -> {:error, nil}
end
end
@doc """
2023-03-29 23:49:45 -04:00
Returns the number of shot rounds for a pack
"""
2023-03-29 22:54:55 -04:00
@spec get_used_count(Pack.t(), User.t()) :: non_neg_integer()
def get_used_count(%Pack{id: pack_id} = pack, user) do
[pack]
|> get_used_counts(user)
2023-03-29 22:54:55 -04:00
|> Map.get(pack_id, 0)
end
@doc """
2023-03-29 23:49:45 -04:00
Returns the number of shot rounds for multiple packs
"""
2023-03-29 22:54:55 -04:00
@spec get_used_counts([Pack.t()], User.t()) ::
%{optional(Pack.id()) => non_neg_integer()}
def get_used_counts(packs, %User{id: user_id}) do
pack_ids =
packs
|> Enum.map(fn %{id: pack_id} -> pack_id end)
Repo.all(
2023-06-05 18:35:19 -04:00
from sr in ShotRecord,
where: sr.pack_id in ^pack_ids,
where: sr.user_id == ^user_id,
group_by: sr.pack_id,
select: {sr.pack_id, sum(sr.count)}
)
|> Map.new()
end
@doc """
2023-03-30 20:43:30 -04:00
Returns the last entered shot record date for a pack
"""
2023-03-29 22:54:55 -04:00
@spec get_last_used_date(Pack.t(), User.t()) :: Date.t() | nil
def get_last_used_date(%Pack{id: pack_id} = pack, user) do
[pack]
|> get_last_used_dates(user)
2023-03-29 22:54:55 -04:00
|> Map.get(pack_id)
end
@doc """
2023-03-30 20:43:30 -04:00
Returns the last entered shot record date for a pack
"""
2023-03-29 22:54:55 -04:00
@spec get_last_used_dates([Pack.t()], User.t()) :: %{optional(Pack.id()) => Date.t()}
def get_last_used_dates(packs, %User{id: user_id}) do
pack_ids =
packs
|> Enum.map(fn %Pack{id: pack_id, user_id: ^user_id} -> pack_id end)
Repo.all(
2023-06-05 18:35:19 -04:00
from sr in ShotRecord,
where: sr.pack_id in ^pack_ids,
where: sr.user_id == ^user_id,
group_by: sr.pack_id,
select: {sr.pack_id, max(sr.date)}
)
|> Map.new()
end
@doc """
2023-03-30 21:53:52 -04:00
Gets the total number of rounds shot for a type
2023-03-30 21:53:52 -04:00
Raises `Ecto.NoResultsError` if the type does not exist.
## Examples
2023-03-30 21:53:52 -04:00
iex> get_used_count_for_type(123, %User{id: 123})
35
2023-03-30 21:53:52 -04:00
iex> get_used_count_for_type(456, %User{id: 123})
** (Ecto.NoResultsError)
"""
2023-03-30 21:53:52 -04:00
@spec get_used_count_for_type(Type.t(), User.t()) :: non_neg_integer()
def get_used_count_for_type(%Type{id: type_id} = type, user) do
[type]
|> get_used_count_for_types(user)
|> Map.get(type_id, 0)
end
@doc """
2023-03-30 21:53:52 -04:00
Gets the total number of rounds shot for multiple types
## Examples
2023-03-30 21:53:52 -04:00
iex> get_used_count_for_types(123, %User{id: 123})
35
"""
2023-03-30 21:53:52 -04:00
@spec get_used_count_for_types([Type.t()], User.t()) ::
%{optional(Type.id()) => non_neg_integer()}
def get_used_count_for_types(types, %User{id: user_id}) do
type_ids =
types
|> Enum.map(fn %Type{id: type_id, user_id: ^user_id} -> type_id end)
Repo.all(
2023-05-12 21:48:04 -04:00
from p in Pack,
2023-06-05 18:35:19 -04:00
left_join: sr in ShotRecord,
on: p.id == sr.pack_id,
2023-05-12 21:48:04 -04:00
where: p.type_id in ^type_ids,
2023-06-05 18:35:19 -04:00
where: not (sr.count |> is_nil()),
2023-05-12 21:48:04 -04:00
group_by: p.type_id,
2023-06-05 18:35:19 -04:00
select: {p.type_id, sum(sr.count)}
)
|> Map.new()
end
2022-02-15 17:33:45 -05:00
end