Skip to content

Commit

Permalink
feat: design v2 (#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gladear authored Oct 9, 2024
1 parent 73d2717 commit 9bd8816
Show file tree
Hide file tree
Showing 236 changed files with 9,448 additions and 7,384 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/composite/test-unit/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ runs:
steps:
- name: Run tests
shell: bash
run: mix test --trace --warnings-as-errors
run: mix test --warnings-as-errors
3 changes: 2 additions & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: Pull Request
# TODO(v2,end) remove feat/design-v2
on:
pull_request:
branches: [main]
branches: [main, feat/design-v2]

jobs:
validate:
Expand Down
146 changes: 3 additions & 143 deletions apps/app/lib/app/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,13 @@ defmodule App.Accounts do

@doc """
Gets a user by email.
## Examples
iex> get_user_by_email("[email protected]")
%User{}
iex> get_user_by_email("[email protected]")
nil
"""
def get_user_by_email(email) when is_binary(email) do
Repo.get_by(User, email: email)
end

@doc """
Gets a user by email and password.
## Examples
iex> get_user_by_email_and_password("[email protected]", "correct_password")
%User{}
iex> get_user_by_email_and_password("[email protected]", "invalid_password")
nil
"""
def get_user_by_email_and_password(email, password)
when is_binary(email) and is_binary(password) do
Expand All @@ -51,31 +33,13 @@ defmodule App.Accounts do
Gets a single user.
Raises `Ecto.NoResultsError` if the User does not exist.
## Examples
iex> get_user!(123)
%User{}
iex> get_user!(456)
** (Ecto.NoResultsError)
"""
def get_user!(id), do: Repo.get!(User, id)

## User registration

@doc """
Registers a user.
## Examples
iex> register_user(%{field: value})
{:ok, %User{}}
iex> register_user(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def register_user(attrs) do
%User{}
Expand All @@ -85,57 +49,15 @@ defmodule App.Accounts do

@doc """
Returns an `%Ecto.Changeset{}` for tracking user changes.
## Examples
iex> change_user_registration(user)
%Ecto.Changeset{data: %User{}}
"""
def change_user_registration(%User{} = user, attrs \\ %{}) do
User.registration_changeset(user, attrs, hash_password: false, validate_email: false)
end

## Settings

@doc """
Returns an `%Ecto.Changeset{}` for changing user display name.
## Examples
iex> change_user_display_name(user)
%Ecto.Changeset{data: %User{}}
"""
def change_user_display_name(user, attrs \\ %{}) do
User.display_name_changeset(user, attrs)
end

@doc """
Updates the user display name.
## Examples
iex> update_user_display_name(user, %{display_name: "valid display name"})
{:ok, %User{}}
iex> update_user_display_name(user, %{display_name: "invalid display name"})
{:error, %Ecto.Changeset{}}
"""
def update_user_display_name(user, attrs) do
user
|> User.display_name_changeset(attrs)
|> Repo.update()
end

@doc """
Returns an `%Ecto.Changeset{}` for changing the user email.
## Examples
iex> change_user_email(user)
%Ecto.Changeset{data: %User{}}
"""
def change_user_email(user, attrs \\ %{}) do
User.email_changeset(user, attrs, validate_email: false)
Expand All @@ -144,15 +66,6 @@ defmodule App.Accounts do
@doc """
Emulates that the email will change without actually changing
it in the database.
## Examples
iex> apply_user_email(user, "valid password", %{email: ...})
{:ok, %User{}}
iex> apply_user_email(user, "invalid password", %{email: ...})
{:error, %Ecto.Changeset{}}
"""
def apply_user_email(user, password, attrs) do
user
Expand Down Expand Up @@ -192,12 +105,6 @@ defmodule App.Accounts do

@doc ~S"""
Delivers the update email instructions to the given user.
## Examples
iex> deliver_user_update_email_instructions(user, current_email, &url(~p"/users/settings/confirm_email/#{&1})")
{:ok, %{to: ..., body: ...}}
"""
def deliver_user_update_email_instructions(%User{} = user, current_email, update_email_url_fun)
when is_function(update_email_url_fun, 1) do
Expand All @@ -209,28 +116,13 @@ defmodule App.Accounts do

@doc """
Returns an `%Ecto.Changeset{}` for changing the user password.
## Examples
iex> change_user_password(user)
%Ecto.Changeset{data: %User{}}
"""
def change_user_password(user, attrs \\ %{}) do
User.password_changeset(user, attrs, hash_password: false)
end

@doc """
Updates the user password.
## Examples
iex> update_user_password(user, "valid password", %{password: ...})
{:ok, %User{}}
iex> update_user_password(user, "invalid password", %{password: ...})
{:error, %Ecto.Changeset{}}
"""
def update_user_password(user, password, attrs) do
changeset =
Expand Down Expand Up @@ -281,15 +173,6 @@ defmodule App.Accounts do

@doc ~S"""
Delivers the confirmation email instructions to the given user.
## Examples
iex> deliver_user_confirmation_instructions(user, &url(~p"/users/confirm/#{&1}"))
{:ok, %{to: ..., body: ...}}
iex> deliver_user_confirmation_instructions(confirmed_user, &url(~p"/users/confirm/#{&1}"))
{:error, :already_confirmed}
"""
def deliver_user_confirmation_instructions(%User{} = user, confirmation_url_fun)
when is_function(confirmation_url_fun, 1) do
Expand All @@ -308,9 +191,10 @@ defmodule App.Accounts do
If the token matches, the user account is marked as confirmed
and the token is deleted.
"""
def confirm_user(token) do
@spec confirm_user(User.t(), String.t()) :: {:ok, User.t()} | :error
def confirm_user(%User{id: id} = _user, token) do
with {:ok, query} <- UserToken.verify_email_token_query(token, "confirm"),
%User{} = user <- Repo.one(query),
%User{id: ^id} = user <- Repo.one(query),
{:ok, %{user: user}} <- Repo.transaction(confirm_user_multi(user)) do
{:ok, user}
else
Expand All @@ -328,12 +212,6 @@ defmodule App.Accounts do

@doc ~S"""
Delivers the reset password email to the given user.
## Examples
iex> deliver_user_reset_password_instructions(user, &url(~p"/users/reset_password/#{&1}"))
{:ok, %{to: ..., body: ...}}
"""
def deliver_user_reset_password_instructions(%User{} = user, reset_password_url_fun)
when is_function(reset_password_url_fun, 1) do
Expand All @@ -344,15 +222,6 @@ defmodule App.Accounts do

@doc """
Gets the user by reset password token.
## Examples
iex> get_user_by_reset_password_token("validtoken")
%User{}
iex> get_user_by_reset_password_token("invalidtoken")
nil
"""
def get_user_by_reset_password_token(token) do
with {:ok, query} <- UserToken.verify_email_token_query(token, "reset_password"),
Expand All @@ -365,15 +234,6 @@ defmodule App.Accounts do

@doc """
Resets the user password.
## Examples
iex> reset_user_password(user, %{password: "new long password", password_confirmation: "new long password"})
{:ok, %User{}}
iex> reset_user_password(user, %{password: "valid", password_confirmation: "not the same"})
{:error, %Ecto.Changeset{}}
"""
def reset_user_password(user, attrs) do
Ecto.Multi.new()
Expand Down
45 changes: 2 additions & 43 deletions apps/app/lib/app/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ defmodule App.Accounts.User do
"""

use Ecto.Schema
import Ecto.Changeset

alias App.Balance.BalanceConfig
import Ecto.Changeset

@type id :: integer()
@type t :: %__MODULE__{
Expand All @@ -15,9 +14,6 @@ defmodule App.Accounts.User do
password: String.t(),
hashed_password: String.t(),
confirmed_at: NaiveDateTime.t(),
display_name: String.t(),
balance_config: BalanceConfig.t(),
balance_config_id: BalanceConfig.id(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
Expand All @@ -29,12 +25,6 @@ defmodule App.Accounts.User do
field :hashed_password, :string, redact: true
field :confirmed_at, :naive_datetime

# display information
field :display_name, :string

# the current balance configuration of the user
belongs_to :balance_config, BalanceConfig

timestamps()
end

Expand Down Expand Up @@ -63,9 +53,8 @@ defmodule App.Accounts.User do
"""
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :display_name, :password])
|> cast(attrs, [:email, :password])
|> validate_email(opts)
|> validate_display_name()
|> validate_password(opts)
end

Expand Down Expand Up @@ -180,34 +169,4 @@ defmodule App.Accounts.User do
add_error(changeset, :current_password, "is not valid")
end
end

@doc """
A user changeset for changing the user display name.
"""
def display_name_changeset(user, attrs) do
user
|> cast(attrs, [:display_name])
|> validate_display_name()
end

defp validate_display_name(changeset) do
changeset
|> validate_required(:display_name)
|> validate_length(:display_name, max: 255)
end

@doc """
A user changeset for changing the user balance config.
"""
@spec balance_config_changeset(t(), map()) :: Ecto.Changeset.t()
def balance_config_changeset(struct, attrs) do
struct
|> cast(attrs, [:balance_config_id])
|> validate_balance_config_id()
end

defp validate_balance_config_id(changeset) do
changeset
|> foreign_key_constraint(:balance_config_id)
end
end
19 changes: 6 additions & 13 deletions apps/app/lib/app/balance.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule App.Balance do
alias App.Transfers
alias App.Transfers.Peer

@type error_reasons :: [%{message: String.t(), uniq_hash: String.t()}]
@type error_reasons :: [%{uniq_hash: String.t(), kind: atom(), extra: map()}]

@doc """
Compute the `:balance` field of book members.
Expand Down Expand Up @@ -128,8 +128,11 @@ defmodule App.Balance do
error_reasons =
Enum.map(peers_without_annual_income, fn peer ->
%{
message: "#{peer.member.display_name} did not set their annual income",
uniq_hash: "income_not_set_#{peer.member_id}"
uniq_hash: "revenues_missing_#{peer.member_id}",
kind: :revenues_missing,
extra: %{
member: peer.member
}
}
end)

Expand Down Expand Up @@ -281,16 +284,6 @@ defmodule App.Balance do
end
end

@doc """
Checks if the computed balance of members does not have errors and is zero.
"""
@spec unbalanced?([BookMember.t()]) :: boolean()
def unbalanced?(members) do
Enum.any?(members, fn member ->
has_balance_error?(member) or not Money.zero?(member.balance)
end)
end

@typedoc """
A type representing a transaction between two members.
This is used to display required operations to balance money between members.
Expand Down
Loading

0 comments on commit 9bd8816

Please sign in to comment.