Skip to content

Commit

Permalink
Merge pull request #136 from bluzky/refactor/better-architecture
Browse files Browse the repository at this point in the history
Apply new architect for Accounts
  • Loading branch information
bluzky authored Feb 11, 2024
2 parents bd1e6e1 + 277b5da commit a85a0c8
Show file tree
Hide file tree
Showing 46 changed files with 590 additions and 1,466 deletions.
17 changes: 17 additions & 0 deletions lib/orange_cms.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ defmodule OrangeCms do
end
end

def query do
quote do
import Ecto.Query, warn: false

alias OrangeCms.Repo
alias OrangeCms.Shared.Filter
end
end

def command do
quote do
import Ecto.Query, warn: false

alias OrangeCms.Repo
end
end

@doc """
When used, dispatch to the appropriate context/schema/service/repo/finder
"""
Expand Down
99 changes: 23 additions & 76 deletions lib/orange_cms/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ defmodule OrangeCms.Accounts do
alias OrangeCms.Accounts.UserNotifier
alias OrangeCms.Accounts.UserToken

def list_users do
User
|> order_by([u], u.first_name)
|> Repo.all()
end
def list_users(filters \\ %{}), do: OrangeCms.Accounts.ListUsersUsecase.call(filters)

def delete_user(user) do
Repo.delete(user)
Expand All @@ -19,37 +15,34 @@ defmodule OrangeCms.Accounts do
## Database getters

@doc """
Gets a user by email.
Gets a user by filter.
## Examples
iex> get_user_by_email("[email protected]")
%User{}
iex> find_user(email: "[email protected]")
{:ok, %User{}}
iex> get_user_by_email("[email protected]")
nil
iex> find_user(email: "[email protected]")
{:error, :not_found}
"""
def get_user_by_email(email) when is_binary(email) do
Repo.get_by(User, email: email)
def find_user(filters) do
OrangeCms.Accounts.FindUserUsecase.call(filters)
end

@doc """
Gets a user by email and password.
## Examples
iex> get_user_by_email_and_password("[email protected]", "correct_password")
%User{}
iex> authorize_user("[email protected]", "correct_password")
{:ok, %User{}}
iex> get_user_by_email_and_password("[email protected]", "invalid_password")
nil
iex> authorize_user("[email protected]", "invalid_password")
{:error, :unauthorized}
"""
def get_user_by_email_and_password(email, password) when is_binary(email) and is_binary(password) do
user = Repo.get_by(User, email: email)
if User.valid_password?(user, password), do: user
end
def authorize_user(email, password), do: OrangeCms.Accounts.AuthorizeUserUsecase.call(email, password)

@doc """
Gets a single user.
Expand All @@ -67,27 +60,13 @@ defmodule OrangeCms.Accounts do
"""
def get_user!(id), do: Repo.get!(User, id)

def search_user(keyword) do
User
|> Filtery.apply(%{email: {:ilike, keyword}})
|> Repo.all()
end

def change_user(%User{} = user, attrs \\ %{}) do
User.changeset(user, attrs)
end

def create_user(attrs) do
%User{}
|> change_user(attrs)
|> Repo.insert()
end
def create_user(attrs), do: OrangeCms.Accounts.CreateUserUsecase.call(attrs)

def update_user(%User{} = user, attrs) do
user
|> change_user(attrs)
|> Repo.update()
end
def update_user(%User{} = user, attrs), do: OrangeCms.Accounts.UpdateUserUsecase.call(user, attrs)

## User registration

Expand All @@ -103,13 +82,10 @@ defmodule OrangeCms.Accounts do
{:error, %Ecto.Changeset{}}
"""
def register_user(attrs) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
end
def register_user(attrs), do: OrangeCms.Accounts.RegisterUserUsecase.call(attrs)

@doc """
TODO: move to application layer
Returns an `%Ecto.Changeset{}` for tracking user changes.
## Examples
Expand All @@ -125,6 +101,7 @@ defmodule OrangeCms.Accounts do
## Settings

@doc """
TODO: move to application layer
Returns an `%Ecto.Changeset{}` for changing the user email.
## Examples
Expand All @@ -138,6 +115,7 @@ defmodule OrangeCms.Accounts do
end

@doc """
TODO: refactor
Emulates that the email will change without actually changing
it in the database.
Expand All @@ -164,26 +142,7 @@ defmodule OrangeCms.Accounts do
The confirmed_at date is also updated to the current time.
"""
def update_user_email(user, token) do
context = "change:#{user.email}"

with {:ok, query} <- UserToken.verify_change_email_token_query(token, context),
%UserToken{sent_to: email} <- Repo.one(query),
{:ok, _} <- Repo.transaction(user_email_multi(user, email, context)) do
:ok
else
_ -> :error
end
end

defp user_email_multi(user, email, context) do
changeset =
user
|> User.email_changeset(%{email: email})
|> User.confirm_changeset()

Ecto.Multi.new()
|> Ecto.Multi.update(:user, changeset)
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
OrangeCms.Accounts.UpdateUserEmailUsecase.call(user, token)
end

@doc ~S"""
Expand Down Expand Up @@ -329,11 +288,8 @@ defmodule OrangeCms.Accounts do
{:ok, %{to: ..., body: ...}}
"""
def deliver_user_reset_password_instructions(%User{} = user, reset_password_url_fun)
when is_function(reset_password_url_fun, 1) do
{encoded_token, user_token} = UserToken.build_email_token(user, "reset_password")
Repo.insert!(user_token)
UserNotifier.deliver_reset_password_instructions(user, reset_password_url_fun.(encoded_token))
def deliver_user_reset_password_instructions(current_email, update_email_url_fun) do
OrangeCms.Accounts.ForgotPasswordUsecase.call(current_email, update_email_url_fun)
end

@doc """
Expand Down Expand Up @@ -369,14 +325,5 @@ defmodule OrangeCms.Accounts do
{:error, %Ecto.Changeset{}}
"""
def reset_user_password(user, attrs) do
Ecto.Multi.new()
|> Ecto.Multi.update(:user, User.password_changeset(user, attrs))
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|> Repo.transaction()
|> case do
{:ok, %{user: user}} -> {:ok, user}
{:error, :user, changeset, _} -> {:error, changeset}
end
end
def reset_user_password(user, attrs), do: OrangeCms.Accounts.ResetUserPasswordUsecase.call(user, attrs)
end
24 changes: 24 additions & 0 deletions lib/orange_cms/accounts/commands/create_user_command.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule OrangeCms.Accounts.CreateUserCommand do
@moduledoc """
Create user command
"""
use OrangeCms, :command

alias OrangeCms.Accounts.User

@spec call(map) :: {:ok, User.t()} | {:error, term}
def call(attrs) do
%User{}
|> User.changeset(attrs)
|> Repo.insert()
|> handle_result()
end

defp handle_result({:ok, user}) do
{:ok, user}
end

defp handle_result({:error, changeset}) do
{:error, changeset}
end
end
24 changes: 24 additions & 0 deletions lib/orange_cms/accounts/commands/register_user_command.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule OrangeCms.Accounts.RegisterUserCommand do
@moduledoc """
Register user command
"""
use OrangeCms, :command

alias OrangeCms.Accounts.User

@spec call(map) :: {:ok, User.t()} | {:error, term}
def call(attrs) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
|> handle_result()
end

defp handle_result({:ok, user}) do
{:ok, user}
end

defp handle_result({:error, changeset}) do
{:error, changeset}
end
end
20 changes: 20 additions & 0 deletions lib/orange_cms/accounts/commands/reset_user_password.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule OrangeCms.Accounts.ResetUserPasswordCommand do
@moduledoc """
This module is responsible for resetting a user's password.
"""
use OrangeCms, :command

alias OrangeCms.Accounts.User
alias OrangeCms.Accounts.UserToken

def call(user, attrs) do
Ecto.Multi.new()
|> Ecto.Multi.update(:user, User.password_changeset(user, attrs))
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|> Repo.transaction()
|> case do
{:ok, %{user: user}} -> {:ok, user}
{:error, :user, changeset, _} -> {:error, changeset}
end
end
end
24 changes: 24 additions & 0 deletions lib/orange_cms/accounts/commands/update_user_command.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule OrangeCms.Accounts.UpdateUserCommand do
@moduledoc """
Update user command
"""
use OrangeCms, :command

alias OrangeCms.Accounts.User

@spec call(User.t(), map) :: {:ok, User.t()} | {:error, term}
def call(user, attrs) do
user
|> User.changeset(attrs)
|> Repo.update()
|> handle_result()
end

defp handle_result({:ok, user}) do
{:ok, user}
end

defp handle_result({:error, changeset}) do
{:error, changeset}
end
end
37 changes: 37 additions & 0 deletions lib/orange_cms/accounts/commands/update_user_email_command.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule OrangeCms.Accounts.UpdateUserEmailCommand do
@moduledoc """
This module is responsible for updating a user's email.
"""
use OrangeCms, :command

alias OrangeCms.Accounts.User
alias OrangeCms.Accounts.UserToken

@doc """
This function is responsible for updating a user's email.
It verifies the token and the context, and then updates the user's email.
"""
@spec call(User.t(), String.t()) :: {:ok, User.t()} | {:error, term()}
def call(user, token) do
context = "change:#{user.email}"

with {:ok, query} <- UserToken.verify_change_email_token_query(token, context),
%UserToken{sent_to: email} <- Repo.one(query),
{:ok, %{user: user}} <- Repo.transaction(user_email_multi(user, email, context)) do
{:ok, user}
else
_ -> {:error, :invalid_token}
end
end

defp user_email_multi(user, email, context) do
changeset =
user
|> User.email_changeset(%{email: email})
|> User.confirm_changeset()

Ecto.Multi.new()
|> Ecto.Multi.update(:user, changeset)
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
end
end
17 changes: 17 additions & 0 deletions lib/orange_cms/accounts/helpers/user_query_builder.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule OrangeCms.Accounts.Helpers.UserQueryBuilder do
@moduledoc """
Helper module to build query for user
"""
alias OrangeCms.Shared.Filter

def base_query do
OrangeCms.Accounts.User
end

@doc """
Build user query from a map of condition
"""
def build(query, filters) do
Filter.with_filters(query, filters)
end
end
File renamed without changes.
File renamed without changes.
21 changes: 21 additions & 0 deletions lib/orange_cms/accounts/queries/find_user_query.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule OrangeCms.Accounts.FindUserQuery do
@moduledoc """
Find user query
"""
use OrangeCms, :query

alias OrangeCms.Accounts.Helpers.UserQueryBuilder
alias OrangeCms.Accounts.User

@spec run(map) :: {:ok, User.t()} | {:error, term}
def run(filters) do
UserQueryBuilder.base_query()
|> build_query(filters)
|> limit(1)
|> Repo.one()
end

defp build_query(query, filters) do
Filter.with_filters(query, filters)
end
end
Loading

0 comments on commit a85a0c8

Please sign in to comment.