Packages
Easy to install Auth system with social integration just in 6 steps on Elixir Phoenix
Current section
Files
Jump to
Current section
Files
lib/client/users/client_user_query.ex
defmodule MishkaAuth.Client.Users.ClientUserQuery do
import Ecto.Query
@type username() :: String.t()
@type password() :: String.t()
@type email() :: String.t()
@type data_uuid() :: Ecto.UUID.t
@type unconfirmed_email() :: email() | nil
@register_params ["name", "lastname", "username", "email", "password"]
@moduledoc """
this module helps us to handle users and connect to users database.
"""
@topic inspect(__MODULE__)
alias MishkaAuth.Helper.Db
alias MishkaAuth.Client.Users.ClientUserSchema
alias MishkaAuth.Helper.Mailer
alias MishkaAuth.Email.Sender
@doc """
this function starts push notification in this module.
"""
@spec subscribe :: :ok | {:error, any}
def subscribe do
Phoenix.PubSub.subscribe(MishkaAuth.get_config_info(:pub_sub), @topic)
end
# this function converts error which is sent by user for showing in real-time app.
defp notify_subscribers({:error, reason}, event) do
{:error, event, reason}
end
defp notify_subscribers({:ok, result}, event) do
Phoenix.PubSub.broadcast(MishkaAuth.get_config_info(:pub_sub), @topic, {__MODULE__, event, result})
Phoenix.PubSub.broadcast(MishkaAuth.get_config_info(:pub_sub), @topic <> "#{result.id}", {__MODULE__, event, result})
end
@doc """
this function accepts info which is created by map and adds user in Users table.
"""
@spec add_user(map()) ::
{:error, :add_user, Ecto.Changeset.t()} | {:ok, :add_user, Ecto.Schema.t()}
def add_user(attrs) do
add = %ClientUserSchema{}
|> ClientUserSchema.changeset(attrs)
|> Db.repo.insert()
notify_subscribers(add, [:user, :add_user])
case add do
{:ok, user_info} -> {:ok, :add_user, user_info}
{:error, changeset} -> {:error, :add_user, changeset}
end
end
@spec update_user(Ecto.Schema.t(), map()) ::
{:error, :update_user, Ecto.Changeset.t()} |
{:ok, :update_user, Ecto.Schema.t()}
defp update_user(old_data, attrs) do
update = ClientUserSchema.changeset(old_data, attrs)
|> Db.repo.update
case update do
{:ok, user_info} -> {:ok, :update_user, user_info}
{:error, changeset} -> {:error, :update_user, changeset}
end
end
@doc """
this function accepts info which is created by map and updates user in Users table.
"""
@spec edit_user(data_uuid(), map()) ::
{:error, :edit_user, :user_doesnt_exist}
| {:ok, :edit_user, Ecto.Schema.t()}
| {:error, :edit_user, :data_input_problem, Ecto.Changeset.t()}
def edit_user(id, attrs) do
with {:ok, :valid_uuid, user_id} <- MishkaAuth.Extra.valid_uuid(id),
{:ok, :find_user_with_user_id, user_info} <- find_user_with_user_id(user_id),
{:ok, :update_user, updated_user_info} <- update_user(user_info, attrs) do
notify_subscribers({:ok, updated_user_info}, [:user, :edit_user])
{:ok, :edit_user, updated_user_info}
else
{:error, :valid_uuid} ->
notify_subscribers({:error, :user_doesnt_exist}, [:user, :edit_user])
{:error, :edit_user, :user_doesnt_exist}
{:error, :update_user, changeset} ->
{:error, :edit_user, :data_input_problem, changeset}
_ ->
notify_subscribers({:error, :user_doesnt_exist}, [:user, :edit_user])
{:error, :edit_user, :user_doesnt_exist}
end
end
# convert Db.repo.delete to atom
@spec remove_user(Ecto.Schema.t()) ::
{:error, :remove_user, Ecto.Changeset.t()} | {:ok, :remove_user, Ecto.Schema.t()}
defp remove_user(user_info) do
case Db.repo.delete(user_info) do
{:ok, struct} -> {:ok, :remove_user, struct}
{:error, changeset} -> {:error, :remove_user, changeset}
end
end
@doc """
this function accepts id which was created and deletes user in Users table.
"""
@spec delete_user(data_uuid()) ::
{:error, :delete_user, :user_doesnt_exist}
| {:ok, :delete_user, Ecto.Schema.t()}
| {:error, :edit_user, :data_input_problem, Ecto.Changeset.t()}
| {:error, :delete_user, :forced_to_delete}
def delete_user(id) do
try do
with {:ok, :valid_uuid, user_id} <- MishkaAuth.Extra.valid_uuid(id),
{:ok, :find_user_with_user_id, user_info} <- find_user_with_user_id(user_id),
{:ok, :remove_user, struct} <- remove_user(user_info) do
notify_subscribers({:ok, struct}, [:user, :delete_user])
{:ok, :delete_user, struct}
else
{:error, :valid_uuid} ->
notify_subscribers({:error, :user_doesnt_exist}, [:user, :delete_user])
{:error, :delete_user, :user_doesnt_exist}
{:error, :remove_user, changeset} ->
{:error, :edit_user, :data_input_problem, changeset}
_ ->
notify_subscribers({:error, :user_doesnt_exist}, [:user, :delete_user])
{:error, :delete_user, :user_doesnt_exist}
end
rescue
_e in Ecto.ConstraintError -> {:error, :delete_user, :forced_to_delete}
end
end
@doc """
get user info with mail
"""
@spec find_user_with_email(email()) ::
{:error, :find_user_with_email} | {:ok, :find_user_with_email, Ecto.Schema.t()}
def find_user_with_email(email) do
case Db.repo.get_by(ClientUserSchema, email: "#{email}") do
nil -> {:error, :find_user_with_email}
user_info -> {:ok, :find_user_with_email, user_info}
end
end
@doc """
get user info with username
"""
@spec find_user_with_username(username()) ::
{:error, :find_user_with_username} | {:ok, :find_user_with_username, Ecto.Schema.t()}
def find_user_with_username(username) do
case Db.repo.get_by(ClientUserSchema, username: "#{username}") do
nil -> {:error, :find_user_with_username}
user_info -> {:ok, :find_user_with_username, user_info}
end
end
@doc """
get user info with id
"""
@spec find_user_with_user_id(data_uuid()) ::
{:error, :find_user_with_user_id} | {:ok, :find_user_with_user_id, Ecto.Schema.t()}
def find_user_with_user_id(id) do
case Db.repo.get(ClientUserSchema, id) do
nil -> {:error, :find_user_with_user_id}
user_info -> {:ok, :find_user_with_user_id, user_info}
end
end
@doc """
this function has two job that can check user existed and user activated. it uses email for this
"""
@spec is_user_activated?(email()) ::
{:error, :is_user_activated?, :user_not_activated | :user_not_found}
| {:ok, :is_user_activated?, Ecto.Schema.t()}
def is_user_activated?(email) do
with {:ok, :find_user_with_email, user_info} <- find_user_with_email(email),
{:ok, :is_unconfirmed_email_nil} <- is_unconfirmed_email_nil?(user_info.unconfirmed_email) do
{:ok, :is_user_activated?, user_info}
else
{:error, :find_user_with_email} -> {:error, :is_user_activated?, :user_not_found}
{:error, :is_unconfirmed_email_nil} -> {:error, :is_user_activated?, :user_not_activated}
end
end
@doc """
this function has two job that can check user existed and user activated. it uses id for this
"""
@spec is_user_activated_with_id?(data_uuid()) ::
{:error, :is_user_activated_with_id?, :user_not_activated | :user_not_found}
| {:ok, :is_user_activated_with_id, Ecto.Schema.t()}
def is_user_activated_with_id?(id) do
with {:ok, :find_user_with_user_id, user_info} <- find_user_with_user_id(id),
{:ok, :is_unconfirmed_email_nil} <- is_unconfirmed_email_nil?(user_info.unconfirmed_email) do
{:ok, :is_user_activated_with_id?, user_info}
else
{:error, :find_user_with_user_id} ->
{:error, :is_user_activated_with_id?, :user_not_found}
{:error, :is_unconfirmed_email_nil} ->
{:error, :is_user_activated_with_id?, :user_not_activated}
end
end
# convert nil or string unconfirmed_email to atom
@spec is_unconfirmed_email_nil?(unconfirmed_email()) ::
{:ok, :is_unconfirmed_email_nil}
| {:error, :is_unconfirmed_email_nil}
defp is_unconfirmed_email_nil?(unconfirmed_email) do
case unconfirmed_email do
nil -> {:ok, :is_unconfirmed_email_nil}
_ -> {:error, :is_unconfirmed_email_nil}
end
end
@spec update_user_password(Ecto.Schema.t(), map()) ::
{:error, :update_user_password, Ecto.Changeset.t()} |
{:ok, :update_user_password, Ecto.Schema.t()}
defp update_user_password(old_data, attrs) do
update = ClientUserSchema.change_password_changeset(old_data, attrs)
|> Db.repo.update
case update do
{:ok, user_update_info} -> {:ok, :update_user_password, user_update_info}
{:error, changeset} -> {:error, :update_user_password, changeset}
end
end
@spec edit_user_password(email(), map()) ::
{:error, :edit_user_password, :user_not_found}
| {:ok, :edit_user_password, Ecto.Schema.t()}
| {:error, :edit_user_password, :data_input_problem, Ecto.Changeset.t()}
@doc """
edit user password with his email
"""
def edit_user_password(email, attrs) do
with {:ok, :find_user_with_email, user_info} <- find_user_with_email(email),
{:ok, :update_user_password, user_update_info} <- update_user_password(user_info, attrs) do
{:ok, :edit_user_password, user_update_info}
else
{:error, :find_user_with_email} ->
{:error, :edit_user_password, :user_not_found}
{:error, :update_user_password, changeset} ->
{:error, :edit_user_password, :data_input_problem, changeset}
end
end
def edit_user_password_with_user_id(user_info, attrs) do
with {:ok, :update_user_password, user_update_info} <- update_user_password(user_info, attrs) do
{:ok, :edit_user_password_with_user_id, user_update_info}
else
{:error, :update_user_password, changeset} ->
{:error, :edit_user_password_with_user_id, :data_input_problem, changeset}
end
end
@spec edit_user_verified_email(email()) ::
{:ok, :edit_user_verified_email} | {:error, :edit_user_password, :user_not_found}
@doc """
systematic user activation
"""
def edit_user_verified_email(email) do
with {:ok, :find_user_with_email, user_info} <- find_user_with_email(email)do
Ecto.Changeset.change(user_info, %{unconfirmed_email: nil})
|> Db.repo.update
{:ok, :edit_user_verified_email}
else
{:error, :find_user_with_email} ->
{:error, :edit_user_verified_email, :user_not_found}
end
end
@doc """
this function can help you check user password is valid or not.
"""
@spec valid_password(Ecto.Schema.t(), password()) :: {:error, :valid_password} | {:ok, :valid_password}
def valid_password(user_info, password) do
case Bcrypt.check_pass(user_info, password) do
{:ok, _params} -> {:ok, :valid_password}
_ -> {:error, :valid_password}
end
end
@spec check_user_and_password(binary, any, :email | :user_id | :username) ::
{:error, :check_user_and_password,
:current_password | :email | :null_password | :user_not_found | :username}
| {:ok, :check_user_and_password, :email | :user_id | :username, Ecto.Schema.t()}
def check_user_and_password(username, password, :username) do
with {:ok, :find_user_with_username, user_info} <- find_user_with_username(username),
{:ok, :chack_password_not_null} <- chack_password_not_null(user_info.password_hash),
{:ok, :valid_password} <- valid_password(user_info, password) do
{:ok, :check_user_and_password, :username, user_info}
else
_ ->
{:error, :check_user_and_password, :username}
end
end
def check_user_and_password(email, password, :email) do
with {:ok, :find_user_with_email, user_info} <- find_user_with_email(email),
{:ok, :chack_password_not_null} <- chack_password_not_null(user_info.password_hash),
{:ok, :valid_password} <- valid_password(user_info, password) do
{:ok, :check_user_and_password, :email, user_info}
else
_ ->
{:error, :check_user_and_password, :email}
end
end
def check_user_and_password(user_id, password, :user_id) do
with {:ok, :find_user_with_user_id, user_info} <- find_user_with_user_id(user_id),
{:ok, :chack_password_not_null} <- chack_password_not_null(user_info.password_hash),
{:ok, :valid_password} <- valid_password(user_info, password) do
{:ok, :check_user_and_password, :user_id, user_info}
else
{:error, :find_user_with_user_id} ->
{:error, :check_user_and_password, :user_not_found}
{:error, :chack_password_not_null} ->
{:error, :check_user_and_password, :null_password}
{:error, :valid_password} ->
{:error, :check_user_and_password, :current_password}
_ ->
{:error, :check_user_and_password, :user_id}
end
end
@spec chack_password_not_null(password()) ::
{:error, :chack_password_not_null} | {:ok, :chack_password_not_null}
def chack_password_not_null(pass) do
if pass == nil, do: {:error, :chack_password_not_null}, else: {:ok, :chack_password_not_null}
end
@spec set_set_systematic_user_data(map, :direct | :social) :: map
def set_set_systematic_user_data(user_params, :direct) do
Map.merge(%{"unconfirmed_email" => user_params["email"]}, MishkaAuth.Extra.strong_params(user_params, @register_params))
end
def set_set_systematic_user_data(user_params, :social) do
Map.merge(%{"unconfirmed_email" => nil, "status" => "active"}, MishkaAuth.Extra.strong_params(user_params, @register_params))
end
def show_public_info_of_user(user_id, :user_id) do
query = from u in ClientUserSchema,
where: u.id == ^user_id,
select: %{
id: u.id,
name: u.name,
lastname: u.lastname,
username: u.username,
email: u.email
}
case Db.repo.one(query) do
nil -> {:error, :show_public_info_of_user, :user_id}
user_info -> {:ok, :show_public_info_of_user, :user_id, user_info}
end
end
def show_public_info_of_user(user_email, :email) do
query = from u in ClientUserSchema,
where: u.email == ^user_email,
select: %{
id: u.id,
name: u.name,
lastname: u.lastname,
username: u.username,
email: u.email
}
case Db.repo.one(query) do
nil -> {:error, :show_public_info_of_user, :email}
user_info -> {:ok, :show_public_info_of_user, :email, user_info}
end
end
@spec edit_user_password_with_user_id(binary, password(), password()) ::
{:error, :edit_user_password_with_user_id,
:current_password | :null_password | :user_not_found}
| {:ok, :edit_user_password_with_user_id, Ecto.Schema.t()}
| {:error, :edit_user_password_with_user_id, :data_input_problem, Ecto.Changeset.t()}
def edit_user_password_with_user_id(user_id, old_password, new_password) do
with {:ok, :check_user_and_password, :user_id, user_info} <- check_user_and_password(user_id, old_password, :user_id),
{:ok, :edit_user_password_with_user_id, user_update_info} <- edit_user_password_with_user_id(user_info, %{password: new_password})
do
{:ok, :edit_user_password_with_user_id, user_update_info}
else
{:error, :check_user_and_password, :user_not_found} ->
{:error, :edit_user_password_with_user_id, :user_not_found}
{:error, :check_user_and_password, :null_password} ->
{:error, :edit_user_password_with_user_id, :null_password}
{:error, :check_user_and_password, :current_password} ->
{:error, :edit_user_password_with_user_id, :current_password}
{:error, :check_user_and_password, :user_id} ->
{:error, :edit_user_password_with_user_id, :unknown_error}
{:error, :edit_user_password_with_user_id, :data_input_problem, changeset} ->
{:error, :edit_user_password_with_user_id, :data_input_problem, changeset}
end
end
@spec add_password(data_uuid(), password()) ::
{:error, :add_password, :password_not_null | :user_not_found}
| {:ok, :add_password, Ecto.Schema.t()}
| {:error, :add_password, :data_input_problem, Ecto.Changeset.t()}
def add_password(user_id, user_password) do
with {:ok, :find_user_with_user_id, user_info} <- find_user_with_user_id(user_id),
{:error, :chack_password_not_null} <- chack_password_not_null(user_info.password_hash),
{:ok, :edit_user_password_with_user_id, user_update_info} <- edit_user_password_with_user_id(user_info, %{password: user_password}) do
{:ok, :add_password, user_update_info}
else
{:error, :find_user_with_user_id} ->
{:error, :add_password, :user_not_found}
{:ok, :chack_password_not_null} ->
{:error, :add_password, :password_not_null}
{:error, :edit_user_password_with_user_id, :data_input_problem, changeset} ->
{:error, :add_password, :data_input_problem, changeset}
end
end
@spec delete_password(data_uuid()) ::
{:error, :delete_password, :null_password | :user_not_found}
| {:ok, :delete_password, Ecto.Schema.t()}
def delete_password(user_id) do
with {:ok, :find_user_with_user_id, user_info} <- find_user_with_user_id(user_id),
{:ok, :chack_password_not_null} <- chack_password_not_null(user_info.password_hash) do
{:ok, changed_user} = Ecto.Changeset.change(user_info, %{password_hash: nil})
|> Db.repo.update
{:ok, :delete_password, changed_user}
else
{:error, :find_user_with_user_id} ->
{:error, :delete_password, :user_not_found}
{:error, :chack_password_not_null} ->
{:error, :delete_password, :null_password}
end
end
# show all user without paginate
@spec show_users :: Ecto.Repo.t() | [Ecto.Schema.t()]
def show_users() do
query = from u in ClientUserSchema,
select: %{
id: u.id,
name: u.name,
lastname: u.lastname,
username: u.username,
email: u.email,
status: u.status,
unconfirmed_email: u.unconfirmed_email,
inserted_at: u.inserted_at,
updated_at: u.updated_at,
}
Db.repo.all(query)
end
# show all user with paginate
@spec show_users(number, number) :: Scrivener.Page.t() | [] | [Ecto.Schema.t()]
def show_users(pagenumber, page_size) do
query = from u in ClientUserSchema,
select: %{
id: u.id,
name: u.name,
lastname: u.lastname,
username: u.username,
email: u.email,
status: u.status,
unconfirmed_email: u.unconfirmed_email,
inserted_at: u.inserted_at,
updated_at: u.updated_at,
}
Db.repo.paginate(query, %{page: pagenumber, page_size: page_size})
end
# show all user with paginate and status, this is force by gaurd cuz we need to know if there is a new EctoEnume to improve it
@spec show_users(number, number, any) :: Scrivener.Page.t() | [] | [Ecto.Schema.t()]
def show_users(pagenumber, page_size, status) when status < 4 do
query = from u in ClientUserSchema,
where: u.status == ^status,
select: %{
id: u.id,
name: u.name,
lastname: u.lastname,
username: u.username,
email: u.email,
status: u.status,
unconfirmed_email: u.unconfirmed_email,
inserted_at: u.inserted_at,
updated_at: u.updated_at,
}
Db.repo.paginate(query, %{page: pagenumber, page_size: page_size})
end
@spec reset_password(email(), String.t(), password(), :email) ::
{:error, :reset_password, :same_code | :user_not_found}
| {:ok, :reset_password, Ecto.Schema.t()}
| {:error, :reset_password, :data_exist | :data_input_problem,
String.t() | Ecto.Changeset.t()}
def reset_password(email, code, new_password, :email) do
with {:ok, :get_data_of_singel_id, record} <- MishkaAuth.RedisClient.get_data_of_singel_id("reset_password", email),
{:ok, :same_code} <- same_code(code, record["code"]),
{:ok, :edit_user_password, user_info} <- edit_user_password(email, %{password: new_password}) do
MishkaAuth.RedisClient.delete_record_of_redis("reset_password", email)
{:ok, :reset_password, user_info}
else
{:error, :edit_user_password, :user_not_found} ->
{:error, :reset_password, :user_not_found}
{:error, :get_all_fields_of_record_redis, msg} ->
{:error, :reset_password, :data_exist, msg}
{:error, :edit_user_password, :data_input_problem, changeset} ->
{:error, :reset_password, :data_input_problem, changeset}
{:error, :same_code} ->
{:error, :reset_password, :same_code}
end
end
@spec reset_password(email(), String.t(), :email) ::
{:error, :reset_password, :find_user_with_email}
| {:ok, :reset_password, Ecto.Schema.t()}
def reset_password(email, country, :email) do
with {:ok, :find_user_with_email, user_info} <- find_user_with_email(email) do
random_code = MishkaAuth.Extra.randstring(8)
MishkaAuth.RedisClient.insert_or_update_into_redis("reset_password", user_info.email, %{
code: random_code,
email: email
}, MishkaAuth.get_config_info(:reset_password_expiration))
Sender.account_email(:reset_password, %{email: user_info.email, subject: "Reset Password", code: random_code}, country)
|> Mailer.mailer.deliver_later
{:ok, :reset_password, user_info}
else
{:error, :find_user_with_email} ->
{:error, :reset_password, :find_user_with_email}
end
end
@spec verify_email(email(), String.t(), :email, :verify) ::
{:ok, :verify_email}
| {:error, :verify_email, :same_code}
| {:error, :verify_email, :data_exist, String.t()}
def verify_email(email, code, :email, :verify) do
with {:ok, :get_data_of_singel_id, record} <- MishkaAuth.RedisClient.get_data_of_singel_id("verify_email", email),
{:ok, :same_code} <- same_code(code, record["code"]),
{:ok, :edit_user_verified_email} <- edit_user_verified_email(email) do
MishkaAuth.RedisClient.delete_record_of_redis("verify_email", email)
{:ok, :verify_email}
else
{:error, :edit_user_verified_email, :user_not_found} ->
{:error, :verify_email, :user_not_found}
{:error, :get_all_fields_of_record_redis, msg} ->
{:error, :verify_email, :data_exist, msg}
{:error, :same_code} ->
{:error, :verify_email, :same_code}
end
end
@spec verify_email(email(), String.t(), :email) ::
{:error, :verify_email, :find_user_with_email}
| {:ok, :verify_email, Ecto.Schema.t()}
def verify_email(email, country, :email) do
with {:ok, :find_user_with_email, user_info} <- find_user_with_email(email) do
random_code = MishkaAuth.Extra.randstring(8)
MishkaAuth.RedisClient.insert_or_update_into_redis("verify_email", user_info.email, %{
code: random_code,
email: email
}, MishkaAuth.get_config_info(:verify_email_expiration))
Sender.account_email(:verify_email, %{email: user_info.email, subject: "Verify Email", code: random_code}, country)
|> Mailer.mailer.deliver_later
{:ok, :verify_email, user_info}
else
{:error, :find_user_with_email} ->
{:error, :verify_email, :find_user_with_email}
end
end
defp same_code(user_code, redis_code) do
if user_code === redis_code, do: {:ok, :same_code} , else: {:error, :same_code}
end
end