forked from shibao/cannery
fix dialyzer, credo and format
This commit is contained in:
parent
f5b3eb4a5e
commit
04d798aaa7
@ -4,8 +4,9 @@ defmodule Cannery.Accounts do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias Cannery.{Repo}
|
alias Cannery.Repo
|
||||||
alias Cannery.Accounts.{User, UserToken, UserNotifier}
|
alias Cannery.Accounts.{User, UserNotifier, UserToken}
|
||||||
|
alias Ecto.{Changeset, Multi}
|
||||||
|
|
||||||
## Database getters
|
## Database getters
|
||||||
|
|
||||||
@ -89,10 +90,10 @@ defmodule Cannery.Accounts do
|
|||||||
{:ok, %User{}}
|
{:ok, %User{}}
|
||||||
|
|
||||||
iex> register_user(%{field: bad_value})
|
iex> register_user(%{field: bad_value})
|
||||||
{:error, %Ecto.Changeset{}}
|
{:error, %Changeset{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec register_user(map()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
@spec register_user(map()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||||
def register_user(attrs) do
|
def register_user(attrs) do
|
||||||
# if no registered users, make first user an admin
|
# if no registered users, make first user an admin
|
||||||
attrs =
|
attrs =
|
||||||
@ -104,15 +105,16 @@ defmodule Cannery.Accounts do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for tracking user changes.
|
Returns an `%Changeset{}` for tracking user changes.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> change_user_registration(user)
|
iex> change_user_registration(user)
|
||||||
%Ecto.Changeset{data: %User{}}
|
%Changeset{data: %User{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec change_user_registration(User.t(), map()) :: Ecto.Changeset.t()
|
@spec change_user_registration(User.t() | User.new_user()) :: Changeset.t()
|
||||||
|
@spec change_user_registration(User.t() | User.new_user(), map()) :: Changeset.t()
|
||||||
def change_user_registration(user, attrs \\ %{}) do
|
def change_user_registration(user, attrs \\ %{}) do
|
||||||
User.registration_changeset(user, attrs, hash_password: false)
|
User.registration_changeset(user, attrs, hash_password: false)
|
||||||
end
|
end
|
||||||
@ -120,29 +122,29 @@ defmodule Cannery.Accounts do
|
|||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for changing the user email.
|
Returns an `%Changeset{}` for changing the user email.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> change_user_email(user)
|
iex> change_user_email(user)
|
||||||
%Ecto.Changeset{data: %User{}}
|
%Changeset{data: %User{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec change_user_email(User.t(), map()) :: Ecto.Changeset.t()
|
@spec change_user_email(User.t(), map()) :: Changeset.t()
|
||||||
def change_user_email(user, attrs \\ %{}) do
|
def change_user_email(user, attrs \\ %{}) do
|
||||||
User.email_changeset(user, attrs)
|
User.email_changeset(user, attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for changing the user role.
|
Returns an `%Changeset{}` for changing the user role.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> change_user_role(user)
|
iex> change_user_role(user)
|
||||||
%Ecto.Changeset{data: %User{}}
|
%Changeset{data: %User{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec change_user_role(User.t(), atom()) :: Ecto.Changeset.t()
|
@spec change_user_role(User.t(), atom()) :: Changeset.t()
|
||||||
def change_user_role(user, role) do
|
def change_user_role(user, role) do
|
||||||
User.role_changeset(user, role)
|
User.role_changeset(user, role)
|
||||||
end
|
end
|
||||||
@ -157,16 +159,16 @@ defmodule Cannery.Accounts do
|
|||||||
{:ok, %User{}}
|
{:ok, %User{}}
|
||||||
|
|
||||||
iex> apply_user_email(user, "invalid password", %{email: ...})
|
iex> apply_user_email(user, "invalid password", %{email: ...})
|
||||||
{:error, %Ecto.Changeset{}}
|
{:error, %Changeset{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec apply_user_email(User.t(), String.t(), map()) ::
|
@spec apply_user_email(User.t(), String.t(), map()) ::
|
||||||
{:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, User.t()} | {:error, Changeset.t()}
|
||||||
def apply_user_email(user, password, attrs) do
|
def apply_user_email(user, password, attrs) do
|
||||||
user
|
user
|
||||||
|> User.email_changeset(attrs)
|
|> User.email_changeset(attrs)
|
||||||
|> User.validate_current_password(password)
|
|> User.validate_current_password(password)
|
||||||
|> Ecto.Changeset.apply_action(:update)
|
|> Changeset.apply_action(:update)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -175,7 +177,7 @@ defmodule Cannery.Accounts do
|
|||||||
If the token matches, the user email is updated and the token is deleted.
|
If the token matches, the user email is updated and the token is deleted.
|
||||||
The confirmed_at date is also updated to the current time.
|
The confirmed_at date is also updated to the current time.
|
||||||
"""
|
"""
|
||||||
@spec update_user_email(User.t(), UserToken.t()) :: {:ok, any()} | :error
|
@spec update_user_email(User.t(), String.t()) :: :ok | :error
|
||||||
def update_user_email(user, token) do
|
def update_user_email(user, token) do
|
||||||
context = "change:#{user.email}"
|
context = "change:#{user.email}"
|
||||||
|
|
||||||
@ -188,12 +190,13 @@ defmodule Cannery.Accounts do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec user_email_multi(User.t(), String.t(), String.t()) :: Multi.t()
|
||||||
defp user_email_multi(user, email, context) do
|
defp user_email_multi(user, email, context) do
|
||||||
changeset = user |> User.email_changeset(%{email: email}) |> User.confirm_changeset()
|
changeset = user |> User.email_changeset(%{email: email}) |> User.confirm_changeset()
|
||||||
|
|
||||||
Ecto.Multi.new()
|
Multi.new()
|
||||||
|> Ecto.Multi.update(:user, changeset)
|
|> Multi.update(:user, changeset)
|
||||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
|
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -216,15 +219,15 @@ defmodule Cannery.Accounts do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for changing the user password.
|
Returns an `%Changeset{}` for changing the user password.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> change_user_password(user)
|
iex> change_user_password(user)
|
||||||
%Ecto.Changeset{data: %User{}}
|
%Changeset{data: %User{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec change_user_password(User.t(), map()) :: Ecto.Changeset.t()
|
@spec change_user_password(User.t(), map()) :: Changeset.t()
|
||||||
def change_user_password(user, attrs \\ %{}) do
|
def change_user_password(user, attrs \\ %{}) do
|
||||||
User.password_changeset(user, attrs, hash_password: false)
|
User.password_changeset(user, attrs, hash_password: false)
|
||||||
end
|
end
|
||||||
@ -238,20 +241,20 @@ defmodule Cannery.Accounts do
|
|||||||
{:ok, %User{}}
|
{:ok, %User{}}
|
||||||
|
|
||||||
iex> update_user_password(user, "invalid password", %{password: ...})
|
iex> update_user_password(user, "invalid password", %{password: ...})
|
||||||
{:error, %Ecto.Changeset{}}
|
{:error, %Changeset{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec update_user_password(User.t(), String.t(), map()) ::
|
@spec update_user_password(User.t(), String.t(), map()) ::
|
||||||
{:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, User.t()} | {:error, Changeset.t()}
|
||||||
def update_user_password(user, password, attrs) do
|
def update_user_password(user, password, attrs) do
|
||||||
changeset =
|
changeset =
|
||||||
user
|
user
|
||||||
|> User.password_changeset(attrs)
|
|> User.password_changeset(attrs)
|
||||||
|> User.validate_current_password(password)
|
|> User.validate_current_password(password)
|
||||||
|
|
||||||
Ecto.Multi.new()
|
Multi.new()
|
||||||
|> Ecto.Multi.update(:user, changeset)
|
|> Multi.update(:user, changeset)
|
||||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, %{user: user}} -> {:ok, user}
|
{:ok, %{user: user}} -> {:ok, user}
|
||||||
@ -269,7 +272,7 @@ defmodule Cannery.Accounts do
|
|||||||
@doc """
|
@doc """
|
||||||
Generates a session token.
|
Generates a session token.
|
||||||
"""
|
"""
|
||||||
@spec generate_user_session_token(User.t()) :: UserToken.t()
|
@spec generate_user_session_token(User.t()) :: String.t()
|
||||||
def generate_user_session_token(user) do
|
def generate_user_session_token(user) do
|
||||||
{token, user_token} = UserToken.build_session_token(user)
|
{token, user_token} = UserToken.build_session_token(user)
|
||||||
Repo.insert!(user_token)
|
Repo.insert!(user_token)
|
||||||
@ -279,7 +282,7 @@ defmodule Cannery.Accounts do
|
|||||||
@doc """
|
@doc """
|
||||||
Gets the user with the given signed token.
|
Gets the user with the given signed token.
|
||||||
"""
|
"""
|
||||||
@spec get_user_by_session_token(UserToken.t()) :: User.t()
|
@spec get_user_by_session_token(String.t()) :: User.t()
|
||||||
def get_user_by_session_token(token) do
|
def get_user_by_session_token(token) do
|
||||||
{:ok, query} = UserToken.verify_session_token_query(token)
|
{:ok, query} = UserToken.verify_session_token_query(token)
|
||||||
Repo.one(query)
|
Repo.one(query)
|
||||||
@ -288,7 +291,7 @@ defmodule Cannery.Accounts do
|
|||||||
@doc """
|
@doc """
|
||||||
Deletes the signed token with the given context.
|
Deletes the signed token with the given context.
|
||||||
"""
|
"""
|
||||||
@spec delete_session_token(UserToken.t()) :: :ok
|
@spec delete_session_token(String.t()) :: :ok
|
||||||
def delete_session_token(token) do
|
def delete_session_token(token) do
|
||||||
Repo.delete_all(UserToken.token_and_context_query(token, "session"))
|
Repo.delete_all(UserToken.token_and_context_query(token, "session"))
|
||||||
:ok
|
:ok
|
||||||
@ -298,7 +301,7 @@ defmodule Cannery.Accounts do
|
|||||||
Returns a boolean if registration is allowed or not
|
Returns a boolean if registration is allowed or not
|
||||||
"""
|
"""
|
||||||
@spec allow_registration?() :: boolean()
|
@spec allow_registration?() :: boolean()
|
||||||
def allow_registration?() do
|
def allow_registration? do
|
||||||
Application.get_env(:cannery, CanneryWeb.Endpoint)[:registration] == "public" or
|
Application.get_env(:cannery, CanneryWeb.Endpoint)[:registration] == "public" or
|
||||||
list_users_by_role(:admin) |> Enum.empty?()
|
list_users_by_role(:admin) |> Enum.empty?()
|
||||||
end
|
end
|
||||||
@ -336,7 +339,7 @@ defmodule Cannery.Accounts do
|
|||||||
If the token matches, the user account is marked as confirmed
|
If the token matches, the user account is marked as confirmed
|
||||||
and the token is deleted.
|
and the token is deleted.
|
||||||
"""
|
"""
|
||||||
@spec confirm_user(UserToken.t()) :: {:ok, User.t()} | atom()
|
@spec confirm_user(String.t()) :: {:ok, User.t()} | atom()
|
||||||
def confirm_user(token) do
|
def confirm_user(token) do
|
||||||
with {:ok, query} <- UserToken.verify_email_token_query(token, "confirm"),
|
with {:ok, query} <- UserToken.verify_email_token_query(token, "confirm"),
|
||||||
%User{} = user <- Repo.one(query),
|
%User{} = user <- Repo.one(query),
|
||||||
@ -348,9 +351,9 @@ defmodule Cannery.Accounts do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp confirm_user_multi(user) do
|
defp confirm_user_multi(user) do
|
||||||
Ecto.Multi.new()
|
Multi.new()
|
||||||
|> Ecto.Multi.update(:user, User.confirm_changeset(user))
|
|> Multi.update(:user, User.confirm_changeset(user))
|
||||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["confirm"]))
|
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["confirm"]))
|
||||||
end
|
end
|
||||||
|
|
||||||
## Reset password
|
## Reset password
|
||||||
@ -385,7 +388,7 @@ defmodule Cannery.Accounts do
|
|||||||
nil
|
nil
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec get_user_by_reset_password_token(UserToken.t()) :: User.t() | nil
|
@spec get_user_by_reset_password_token(String.t()) :: User.t() | nil
|
||||||
def get_user_by_reset_password_token(token) do
|
def get_user_by_reset_password_token(token) do
|
||||||
with {:ok, query} <- UserToken.verify_email_token_query(token, "reset_password"),
|
with {:ok, query} <- UserToken.verify_email_token_query(token, "reset_password"),
|
||||||
%User{} = user <- Repo.one(query) do
|
%User{} = user <- Repo.one(query) do
|
||||||
@ -404,14 +407,14 @@ defmodule Cannery.Accounts do
|
|||||||
{:ok, %User{}}
|
{:ok, %User{}}
|
||||||
|
|
||||||
iex> reset_user_password(user, %{password: "valid", password_confirmation: "not the same"})
|
iex> reset_user_password(user, %{password: "valid", password_confirmation: "not the same"})
|
||||||
{:error, %Ecto.Changeset{}}
|
{:error, %Changeset{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec reset_user_password(User.t(), map()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
@spec reset_user_password(User.t(), map()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||||
def reset_user_password(user, attrs) do
|
def reset_user_password(user, attrs) do
|
||||||
Ecto.Multi.new()
|
Multi.new()
|
||||||
|> Ecto.Multi.update(:user, User.password_changeset(user, attrs))
|
|> Multi.update(:user, User.password_changeset(user, attrs))
|
||||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, %{user: user}} -> {:ok, user}
|
{:ok, %{user: user}} -> {:ok, user}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
defmodule Cannery.Accounts.User do
|
defmodule Cannery.Accounts.User do
|
||||||
|
@moduledoc """
|
||||||
|
A cannery user
|
||||||
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Cannery.Accounts.{User}
|
alias Ecto.{Changeset, UUID}
|
||||||
alias Cannery.Invites.{Invite}
|
alias Cannery.{Accounts.User, Invites.Invite}
|
||||||
|
|
||||||
@derive {Inspect, except: [:password]}
|
@derive {Inspect, except: [:password]}
|
||||||
@primary_key {:id, :binary_id, autogenerate: true}
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
@ -19,8 +23,8 @@ defmodule Cannery.Accounts.User do
|
|||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@type t :: %{
|
@type t :: %User{
|
||||||
id: Ecto.UUID.t(),
|
id: UUID.t(),
|
||||||
email: String.t(),
|
email: String.t(),
|
||||||
password: String.t(),
|
password: String.t(),
|
||||||
hashed_password: String.t(),
|
hashed_password: String.t(),
|
||||||
@ -31,6 +35,8 @@ defmodule Cannery.Accounts.User do
|
|||||||
updated_at: NaiveDateTime.t()
|
updated_at: NaiveDateTime.t()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@type new_user :: %User{}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
A user changeset for registration.
|
A user changeset for registration.
|
||||||
|
|
||||||
@ -48,8 +54,8 @@ defmodule Cannery.Accounts.User do
|
|||||||
validations on a LiveView form), this option can be set to `false`.
|
validations on a LiveView form), this option can be set to `false`.
|
||||||
Defaults to `true`.
|
Defaults to `true`.
|
||||||
"""
|
"""
|
||||||
@spec registration_changeset(User.t(), map()) :: Ecto.Changeset.t()
|
@spec registration_changeset(User.t() | User.new_user(), map()) :: Changeset.t()
|
||||||
@spec registration_changeset(User.t(), map(), keyword()) :: Ecto.Changeset.t()
|
@spec registration_changeset(User.t() | User.new_user(), map(), keyword()) :: Changeset.t()
|
||||||
def registration_changeset(user, attrs, opts \\ []) do
|
def registration_changeset(user, attrs, opts \\ []) do
|
||||||
user
|
user
|
||||||
|> cast(attrs, [:email, :password, :role])
|
|> cast(attrs, [:email, :password, :role])
|
||||||
@ -61,12 +67,12 @@ defmodule Cannery.Accounts.User do
|
|||||||
A user changeset for role.
|
A user changeset for role.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec role_changeset(User.t(), atom()) :: Ecto.Changeset.t()
|
@spec role_changeset(User.t(), atom()) :: Changeset.t()
|
||||||
def role_changeset(user, role) do
|
def role_changeset(user, role) do
|
||||||
user |> cast(%{"role" => role}, [:role])
|
user |> cast(%{"role" => role}, [:role])
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec validate_email(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
@spec validate_email(Changeset.t()) :: Changeset.t()
|
||||||
defp validate_email(changeset) do
|
defp validate_email(changeset) do
|
||||||
changeset
|
changeset
|
||||||
|> validate_required([:email])
|
|> validate_required([:email])
|
||||||
@ -76,7 +82,7 @@ defmodule Cannery.Accounts.User do
|
|||||||
|> unique_constraint(:email)
|
|> unique_constraint(:email)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec validate_password(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
|
@spec validate_password(Changeset.t(), keyword()) :: Changeset.t()
|
||||||
defp validate_password(changeset, opts) do
|
defp validate_password(changeset, opts) do
|
||||||
changeset
|
changeset
|
||||||
|> validate_required([:password])
|
|> validate_required([:password])
|
||||||
@ -87,7 +93,7 @@ defmodule Cannery.Accounts.User do
|
|||||||
|> maybe_hash_password(opts)
|
|> maybe_hash_password(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec maybe_hash_password(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
|
@spec maybe_hash_password(Changeset.t(), keyword()) :: Changeset.t()
|
||||||
defp maybe_hash_password(changeset, opts) do
|
defp maybe_hash_password(changeset, opts) do
|
||||||
hash_password? = Keyword.get(opts, :hash_password, true)
|
hash_password? = Keyword.get(opts, :hash_password, true)
|
||||||
password = get_change(changeset, :password)
|
password = get_change(changeset, :password)
|
||||||
@ -106,7 +112,7 @@ defmodule Cannery.Accounts.User do
|
|||||||
|
|
||||||
It requires the email to change otherwise an error is added.
|
It requires the email to change otherwise an error is added.
|
||||||
"""
|
"""
|
||||||
@spec email_changeset(User.t(), map()) :: Ecto.Changeset.t()
|
@spec email_changeset(User.t(), map()) :: Changeset.t()
|
||||||
def email_changeset(user, attrs) do
|
def email_changeset(user, attrs) do
|
||||||
user
|
user
|
||||||
|> cast(attrs, [:email])
|
|> cast(attrs, [:email])
|
||||||
@ -129,8 +135,8 @@ defmodule Cannery.Accounts.User do
|
|||||||
validations on a LiveView form), this option can be set to `false`.
|
validations on a LiveView form), this option can be set to `false`.
|
||||||
Defaults to `true`.
|
Defaults to `true`.
|
||||||
"""
|
"""
|
||||||
@spec password_changeset(User.t(), map()) :: Ecto.Changeset.t()
|
@spec password_changeset(User.t(), map()) :: Changeset.t()
|
||||||
@spec password_changeset(User.t(), map(), keyword()) :: Ecto.Changeset.t()
|
@spec password_changeset(User.t(), map(), keyword()) :: Changeset.t()
|
||||||
def password_changeset(user, attrs, opts \\ []) do
|
def password_changeset(user, attrs, opts \\ []) do
|
||||||
user
|
user
|
||||||
|> cast(attrs, [:password])
|
|> cast(attrs, [:password])
|
||||||
@ -141,10 +147,10 @@ defmodule Cannery.Accounts.User do
|
|||||||
@doc """
|
@doc """
|
||||||
Confirms the account by setting `confirmed_at`.
|
Confirms the account by setting `confirmed_at`.
|
||||||
"""
|
"""
|
||||||
@spec confirm_changeset(User.t()) :: Ecto.Changeset.t()
|
@spec confirm_changeset(User.t() | Changeset.t()) :: Changeset.t()
|
||||||
def confirm_changeset(user) do
|
def confirm_changeset(user_or_changeset) do
|
||||||
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
|
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
|
||||||
change(user, confirmed_at: now)
|
user_or_changeset |> change(confirmed_at: now)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -167,12 +173,10 @@ defmodule Cannery.Accounts.User do
|
|||||||
@doc """
|
@doc """
|
||||||
Validates the current password otherwise adds an error to the changeset.
|
Validates the current password otherwise adds an error to the changeset.
|
||||||
"""
|
"""
|
||||||
@spec validate_current_password(Ecto.Changeset.t(), String.t()) :: Ecto.UUID.t()
|
@spec validate_current_password(Changeset.t(), String.t()) :: Changeset.t()
|
||||||
def validate_current_password(changeset, password) do
|
def validate_current_password(changeset, password) do
|
||||||
if valid_password?(changeset.data, password) do
|
if valid_password?(changeset.data, password),
|
||||||
changeset
|
do: changeset,
|
||||||
else
|
else: changeset |> add_error(:current_password, "is not valid")
|
||||||
add_error(changeset, :current_password, "is not valid")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule Cannery.Accounts.UserNotifier do
|
defmodule Cannery.Accounts.UserNotifier do
|
||||||
|
@moduledoc """
|
||||||
|
Contains all user emails and notifications
|
||||||
|
"""
|
||||||
|
|
||||||
# For simplicity, this module simply logs messages to the terminal.
|
# For simplicity, this module simply logs messages to the terminal.
|
||||||
# You should replace it by a proper email or notification tool, such as:
|
# You should replace it by a proper email or notification tool, such as:
|
||||||
#
|
#
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
defmodule Cannery.Accounts.UserToken do
|
defmodule Cannery.Accounts.UserToken do
|
||||||
|
@moduledoc """
|
||||||
|
Schema for serialized user session and authentication tokens
|
||||||
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
alias Cannery.{Accounts}
|
alias Ecto.{Query, UUID}
|
||||||
|
alias Cannery.{Accounts.User, Accounts.UserToken}
|
||||||
|
|
||||||
@hash_algorithm :sha256
|
@hash_algorithm :sha256
|
||||||
@rand_size 32
|
@rand_size 32
|
||||||
@ -19,19 +24,33 @@ defmodule Cannery.Accounts.UserToken do
|
|||||||
field :token, :binary
|
field :token, :binary
|
||||||
field :context, :string
|
field :context, :string
|
||||||
field :sent_to, :string
|
field :sent_to, :string
|
||||||
belongs_to :user, Accounts.User
|
|
||||||
|
belongs_to :user, User
|
||||||
|
|
||||||
timestamps(updated_at: false)
|
timestamps(updated_at: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@type t :: %UserToken{
|
||||||
|
id: UUID.t(),
|
||||||
|
token: String.t(),
|
||||||
|
context: String.t(),
|
||||||
|
sent_to: String.t(),
|
||||||
|
user: User.t(),
|
||||||
|
user_id: UUID.t(),
|
||||||
|
inserted_at: NaiveDateTime.t()
|
||||||
|
}
|
||||||
|
|
||||||
|
@type new_token :: %UserToken{}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Generates a token that will be stored in a signed place,
|
Generates a token that will be stored in a signed place,
|
||||||
such as session or cookie. As they are signed, those
|
such as session or cookie. As they are signed, those
|
||||||
tokens do not need to be hashed.
|
tokens do not need to be hashed.
|
||||||
"""
|
"""
|
||||||
def build_session_token(user) do
|
@spec build_session_token(User.t()) :: {token :: String.t(), UserToken.new_token()}
|
||||||
|
def build_session_token(%{id: user_id}) do
|
||||||
token = :crypto.strong_rand_bytes(@rand_size)
|
token = :crypto.strong_rand_bytes(@rand_size)
|
||||||
{token, %Accounts.UserToken{token: token, context: "session", user_id: user.id}}
|
{token, %UserToken{token: token, context: "session", user_id: user_id}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -39,6 +58,7 @@ defmodule Cannery.Accounts.UserToken do
|
|||||||
|
|
||||||
The query returns the user found by the token.
|
The query returns the user found by the token.
|
||||||
"""
|
"""
|
||||||
|
@spec verify_session_token_query(String.t()) :: {:ok, Query.t()}
|
||||||
def verify_session_token_query(token) do
|
def verify_session_token_query(token) do
|
||||||
query =
|
query =
|
||||||
from token in token_and_context_query(token, "session"),
|
from token in token_and_context_query(token, "session"),
|
||||||
@ -57,16 +77,19 @@ defmodule Cannery.Accounts.UserToken do
|
|||||||
The token is valid for a week as long as users don't change
|
The token is valid for a week as long as users don't change
|
||||||
their email.
|
their email.
|
||||||
"""
|
"""
|
||||||
|
@spec build_email_token(User.t(), String.t()) :: {String.t(), UserToken.new_token()}
|
||||||
def build_email_token(user, context) do
|
def build_email_token(user, context) do
|
||||||
build_hashed_token(user, context, user.email)
|
build_hashed_token(user, context, user.email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec build_hashed_token(User.t(), String.t(), String.t()) ::
|
||||||
|
{String.t(), UserToken.new_token()}
|
||||||
defp build_hashed_token(user, context, sent_to) do
|
defp build_hashed_token(user, context, sent_to) do
|
||||||
token = :crypto.strong_rand_bytes(@rand_size)
|
token = :crypto.strong_rand_bytes(@rand_size)
|
||||||
hashed_token = :crypto.hash(@hash_algorithm, token)
|
hashed_token = :crypto.hash(@hash_algorithm, token)
|
||||||
|
|
||||||
{Base.url_encode64(token, padding: false),
|
{Base.url_encode64(token, padding: false),
|
||||||
%Accounts.UserToken{
|
%UserToken{
|
||||||
token: hashed_token,
|
token: hashed_token,
|
||||||
context: context,
|
context: context,
|
||||||
sent_to: sent_to,
|
sent_to: sent_to,
|
||||||
@ -79,6 +102,7 @@ defmodule Cannery.Accounts.UserToken do
|
|||||||
|
|
||||||
The query returns the user found by the token.
|
The query returns the user found by the token.
|
||||||
"""
|
"""
|
||||||
|
@spec verify_email_token_query(String.t(), String.t()) :: {:ok, Query.t()} | :error
|
||||||
def verify_email_token_query(token, context) do
|
def verify_email_token_query(token, context) do
|
||||||
case Base.url_decode64(token, padding: false) do
|
case Base.url_decode64(token, padding: false) do
|
||||||
{:ok, decoded_token} ->
|
{:ok, decoded_token} ->
|
||||||
@ -98,6 +122,7 @@ defmodule Cannery.Accounts.UserToken do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec days_for_context(<<_::56>>) :: non_neg_integer()
|
||||||
defp days_for_context("confirm"), do: @confirm_validity_in_days
|
defp days_for_context("confirm"), do: @confirm_validity_in_days
|
||||||
defp days_for_context("reset_password"), do: @reset_password_validity_in_days
|
defp days_for_context("reset_password"), do: @reset_password_validity_in_days
|
||||||
|
|
||||||
@ -106,6 +131,7 @@ defmodule Cannery.Accounts.UserToken do
|
|||||||
|
|
||||||
The query returns the user token record.
|
The query returns the user token record.
|
||||||
"""
|
"""
|
||||||
|
@spec verify_change_email_token_query(String.t(), String.t()) :: {:ok, Query.t()} | :error
|
||||||
def verify_change_email_token_query(token, context) do
|
def verify_change_email_token_query(token, context) do
|
||||||
case Base.url_decode64(token, padding: false) do
|
case Base.url_decode64(token, padding: false) do
|
||||||
{:ok, decoded_token} ->
|
{:ok, decoded_token} ->
|
||||||
@ -125,18 +151,20 @@ defmodule Cannery.Accounts.UserToken do
|
|||||||
@doc """
|
@doc """
|
||||||
Returns the given token with the given context.
|
Returns the given token with the given context.
|
||||||
"""
|
"""
|
||||||
|
@spec token_and_context_query(String.t(), String.t()) :: Query.t()
|
||||||
|
@spec token_and_context_query(User.t(), :all | nonempty_maybe_improper_list()) :: Query.t()
|
||||||
def token_and_context_query(token, context) do
|
def token_and_context_query(token, context) do
|
||||||
from Accounts.UserToken, where: [token: ^token, context: ^context]
|
from UserToken, where: [token: ^token, context: ^context]
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets all tokens for the given user for the given contexts.
|
Gets all tokens for the given user for the given contexts.
|
||||||
"""
|
"""
|
||||||
def user_and_contexts_query(user, :all) do
|
def user_and_contexts_query(%{id: user_id}, :all) do
|
||||||
from t in Accounts.UserToken, where: t.user_id == ^user.id
|
from t in UserToken, where: t.user_id == ^user_id
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_and_contexts_query(user, [_ | _] = contexts) do
|
def user_and_contexts_query(%{id: user_id}, [_ | _] = contexts) do
|
||||||
from t in Accounts.UserToken, where: t.user_id == ^user.id and t.context in ^contexts
|
from t in UserToken, where: t.user_id == ^user_id and t.context in ^contexts
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
defmodule Cannery.Ammo.AmmoGroup do
|
defmodule Cannery.Ammo.AmmoGroup do
|
||||||
|
@moduledoc """
|
||||||
|
A group of a certain ammunition type.
|
||||||
|
|
||||||
|
Can be placed in a container, and contains auxiliary information such as the
|
||||||
|
amount paid for that ammunition, or what condition it is in
|
||||||
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Cannery.{Accounts, Ammo, Containers, Tags}
|
alias Cannery.{Accounts, Ammo, Containers, Tags}
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
defmodule Cannery.Ammo.AmmoType do
|
defmodule Cannery.Ammo.AmmoType do
|
||||||
|
@moduledoc """
|
||||||
|
An ammunition type.
|
||||||
|
|
||||||
|
Contains statistical information about the ammunition.
|
||||||
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@ -4,9 +4,8 @@ defmodule Cannery.Containers do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias Cannery.Repo
|
alias Cannery.{Containers.Container, Repo}
|
||||||
|
alias Ecto.{Changeset, UUID}
|
||||||
alias Cannery.Containers.Container
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the list of containers.
|
Returns the list of containers.
|
||||||
@ -17,6 +16,7 @@ defmodule Cannery.Containers do
|
|||||||
[%Container{}, ...]
|
[%Container{}, ...]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@spec list_containers() :: [Container.t()]
|
||||||
def list_containers do
|
def list_containers do
|
||||||
Repo.all(Container)
|
Repo.all(Container)
|
||||||
end
|
end
|
||||||
@ -35,6 +35,7 @@ defmodule Cannery.Containers do
|
|||||||
** (Ecto.NoResultsError)
|
** (Ecto.NoResultsError)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@spec get_container!(container_id :: UUID.t()) :: Container.t()
|
||||||
def get_container!(id), do: Repo.get!(Container, id)
|
def get_container!(id), do: Repo.get!(Container, id)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -49,10 +50,9 @@ defmodule Cannery.Containers do
|
|||||||
{:error, %Ecto.Changeset{}}
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def create_container(attrs \\ %{}) do
|
@spec create_container(attrs :: map()) :: {:ok, Container.t()} | {:error, Changeset.t()}
|
||||||
%Container{}
|
def create_container(attrs) do
|
||||||
|> Container.changeset(attrs)
|
%Container{} |> Container.changeset(attrs) |> Repo.insert()
|
||||||
|> Repo.insert()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -67,10 +67,10 @@ defmodule Cannery.Containers do
|
|||||||
{:error, %Ecto.Changeset{}}
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def update_container(%Container{} = container, attrs) do
|
@spec update_container(Container.t() | Ecto.Changeset.t(), map()) ::
|
||||||
container
|
{:ok, Container.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|> Container.changeset(attrs)
|
def update_container(container, attrs) do
|
||||||
|> Repo.update()
|
container |> Container.changeset(attrs) |> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -85,9 +85,9 @@ defmodule Cannery.Containers do
|
|||||||
{:error, %Ecto.Changeset{}}
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def delete_container(%Container{} = container) do
|
@spec delete_container(Container.t() | Ecto.Changeset.t()) ::
|
||||||
Repo.delete(container)
|
{:ok, Container.t()} | {:error, Ecto.Changeset.t()}
|
||||||
end
|
def delete_container(container), do: Repo.delete(container)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for tracking container changes.
|
Returns an `%Ecto.Changeset{}` for tracking container changes.
|
||||||
@ -97,8 +97,11 @@ defmodule Cannery.Containers do
|
|||||||
iex> change_container(container)
|
iex> change_container(container)
|
||||||
%Ecto.Changeset{data: %Container{}}
|
%Ecto.Changeset{data: %Container{}}
|
||||||
|
|
||||||
|
iex> change_container(%Ecto.Changeset{})
|
||||||
|
%Ecto.Changeset{data: %Container{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def change_container(%Container{} = container, attrs \\ %{}) do
|
@spec change_container(Container.t()) :: Changeset.t()
|
||||||
Container.changeset(container, attrs)
|
@spec change_container(Container.t(), map()) :: Changeset.t()
|
||||||
end
|
def change_container(container, attrs \\ %{}), do: container |> Container.changeset(attrs)
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
defmodule Cannery.Containers.Container do
|
defmodule Cannery.Containers.Container do
|
||||||
|
@moduledoc """
|
||||||
|
A container that holds ammunition and belongs to a user.
|
||||||
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Cannery.{Accounts}
|
alias Ecto.{Changeset, UUID}
|
||||||
|
alias Cannery.{Accounts.User, Containers.Container}
|
||||||
|
|
||||||
@primary_key {:id, :binary_id, autogenerate: true}
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
@foreign_key_type :binary_id
|
@foreign_key_type :binary_id
|
||||||
@ -11,27 +16,30 @@ defmodule Cannery.Containers.Container do
|
|||||||
field :location, :string
|
field :location, :string
|
||||||
field :type, :string
|
field :type, :string
|
||||||
|
|
||||||
belongs_to :user, Accounts.User
|
belongs_to :user, User
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@type t :: %{
|
@type t :: %Container{
|
||||||
id: Ecto.UUID.t(),
|
id: UUID.t(),
|
||||||
name: String.t(),
|
name: String.t(),
|
||||||
desc: String.t(),
|
desc: String.t(),
|
||||||
location: String.t(),
|
location: String.t(),
|
||||||
type: String.t(),
|
type: String.t(),
|
||||||
user: Accounts.User.t(),
|
user: User.t(),
|
||||||
user_id: Ecto.UUID.t(),
|
user_id: UUID.t(),
|
||||||
inserted_at: NaiveDateTime.t(),
|
inserted_at: NaiveDateTime.t(),
|
||||||
updated_at: NaiveDateTime.t()
|
updated_at: NaiveDateTime.t()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@type new_container :: %Container{}
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@spec changeset(Container.t() | Container.new_container(), map()) :: Changeset.t()
|
||||||
def changeset(container, attrs) do
|
def changeset(container, attrs) do
|
||||||
container
|
container
|
||||||
|> cast(attrs, [:name, :desc, :type, :location, :user_id])
|
|> cast(attrs, [:name, :desc, :type, :location, :user_id])
|
||||||
|> validate_required([:name, :desc, :type, :location, :user_id])
|
|> validate_required([:name, :type, :user_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,28 +1,34 @@
|
|||||||
defmodule Cannery.Containers.ContainerTag do
|
defmodule Cannery.Containers.ContainerTag do
|
||||||
|
@moduledoc """
|
||||||
|
Thru-table struct for associating Cannery.Containers.Container and
|
||||||
|
Cannery.Tags.Tag.
|
||||||
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Cannery.{Containers, Tags}
|
alias Cannery.{Containers.Container, Containers.ContainerTag, Tags.Tag}
|
||||||
|
|
||||||
@primary_key {:id, :binary_id, autogenerate: true}
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
@foreign_key_type :binary_id
|
@foreign_key_type :binary_id
|
||||||
schema "container_tags" do
|
schema "container_tags" do
|
||||||
belongs_to :container, Containers.Container
|
belongs_to :container, Container
|
||||||
belongs_to :tag, Tags.Tag
|
belongs_to :tag, Tag
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@type t :: %{
|
@type t :: %ContainerTag{
|
||||||
id: Ecto.UUID.t(),
|
id: Ecto.UUID.t(),
|
||||||
container: Containers.Container.t(),
|
container: Container.t(),
|
||||||
container_id: Ecto.UUID.t(),
|
container_id: Ecto.UUID.t(),
|
||||||
tag: Tags.Tag.t(),
|
tag: Tag.t(),
|
||||||
tag_id: Ecto.UUID.t(),
|
tag_id: Ecto.UUID.t(),
|
||||||
inserted_at: NaiveDateTime.t(),
|
inserted_at: NaiveDateTime.t(),
|
||||||
updated_at: NaiveDateTime.t()
|
updated_at: NaiveDateTime.t()
|
||||||
}
|
}
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@spec changeset(ContainerTag.t(), map()) :: Ecto.Changeset.t()
|
||||||
def changeset(container_tag, attrs) do
|
def changeset(container_tag, attrs) do
|
||||||
container_tag
|
container_tag
|
||||||
|> cast(attrs, [:tag_id, :container_id])
|
|> cast(attrs, [:tag_id, :container_id])
|
||||||
|
@ -4,8 +4,8 @@ defmodule Cannery.Invites do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias Cannery.{Accounts, Repo}
|
alias Ecto.{Changeset, UUID}
|
||||||
alias Cannery.Invites.Invite
|
alias Cannery.{Accounts.User, Invites.Invite, Repo}
|
||||||
|
|
||||||
@invite_token_length 20
|
@invite_token_length 20
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ defmodule Cannery.Invites do
|
|||||||
iex> get_invite_by_token("invalid_token")
|
iex> get_invite_by_token("invalid_token")
|
||||||
nil
|
nil
|
||||||
"""
|
"""
|
||||||
@spec get_invite_by_token(String.t()) :: Invite.t() | nil
|
@spec get_invite_by_token(String.t() | nil) :: Invite.t() | nil
|
||||||
def get_invite_by_token(nil), do: nil
|
def get_invite_by_token(nil), do: nil
|
||||||
def get_invite_by_token(""), do: nil
|
def get_invite_by_token(""), do: nil
|
||||||
|
|
||||||
@ -86,22 +86,22 @@ defmodule Cannery.Invites do
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> create_invite(%Accounts.User{id: "1"}, %{field: value})
|
iex> create_invite(%User{id: "1"}, %{field: value})
|
||||||
{:ok, %Invite{}}
|
{:ok, %Invite{}}
|
||||||
|
|
||||||
iex> create_invite("1", %{field: value})
|
iex> create_invite("1", %{field: value})
|
||||||
{:ok, %Invite{}}
|
{:ok, %Invite{}}
|
||||||
|
|
||||||
iex> create_invite(%Accounts.User{id: "1"}, %{field: bad_value})
|
iex> create_invite(%User{id: "1"}, %{field: bad_value})
|
||||||
{:error, %Ecto.Changeset{}}
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec create_invite(user_or_user_id :: Accounts.User.t() | Ecto.UUID.t(), attrs :: map()) ::
|
@spec create_invite(user :: User.t(), attrs :: map()) ::
|
||||||
Invite.t()
|
{:ok, Invite.t()} | {:error, Changeset.t()}
|
||||||
def create_invite(%{id: user_id}, attrs) do
|
def create_invite(%{id: user_id}, attrs), do: create_invite(user_id, attrs)
|
||||||
create_invite(user_id, attrs)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
@spec create_invite(user_id :: UUID.t(), attrs :: map()) ::
|
||||||
|
{:ok, Invite.t()} | {:error, Changeset.t()}
|
||||||
def create_invite(user_id, attrs) when not (user_id |> is_nil()) do
|
def create_invite(user_id, attrs) when not (user_id |> is_nil()) do
|
||||||
attrs =
|
attrs =
|
||||||
attrs
|
attrs
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
defmodule Cannery.Invites.Invite do
|
defmodule Cannery.Invites.Invite do
|
||||||
|
@moduledoc """
|
||||||
|
An invite, created by an admin to allow someone to join their instance. An
|
||||||
|
invite can be enabled or disabled, and can have an optional number of uses if
|
||||||
|
`:uses_left` is defined.
|
||||||
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Cannery.{Accounts}
|
alias Ecto.{Changeset, UUID}
|
||||||
|
alias Cannery.{Accounts.User, Invites.Invite}
|
||||||
|
|
||||||
@primary_key {:id, :binary_id, autogenerate: true}
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
@foreign_key_type :binary_id
|
@foreign_key_type :binary_id
|
||||||
@ -11,22 +18,27 @@ defmodule Cannery.Invites.Invite do
|
|||||||
field :uses_left, :integer, default: nil
|
field :uses_left, :integer, default: nil
|
||||||
field :disabled_at, :naive_datetime
|
field :disabled_at, :naive_datetime
|
||||||
|
|
||||||
belongs_to :user, Accounts.User
|
belongs_to :user, User
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@type t :: %{
|
@type t :: %Invite{
|
||||||
id: Ecto.UUID.t(),
|
id: UUID.t(),
|
||||||
name: String.t(),
|
name: String.t(),
|
||||||
token: String.t(),
|
token: String.t(),
|
||||||
uses_left: integer() | nil,
|
uses_left: integer() | nil,
|
||||||
disabled_at: NaiveDateTime.t(),
|
disabled_at: NaiveDateTime.t(),
|
||||||
user: Accounts.User.t(),
|
user: User.t(),
|
||||||
user_id: Ecto.UUID.t()
|
user_id: UUID.t(),
|
||||||
|
inserted_at: NaiveDateTime.t(),
|
||||||
|
updated_at: NaiveDateTime.t()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@type new_invite :: %Invite{}
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@spec changeset(Invite.t() | Invite.new_invite(), map()) :: Changeset.t()
|
||||||
def changeset(invite, attrs) do
|
def changeset(invite, attrs) do
|
||||||
invite
|
invite
|
||||||
|> cast(attrs, [:name, :token, :uses_left, :disabled_at, :user_id])
|
|> cast(attrs, [:name, :token, :uses_left, :disabled_at, :user_id])
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
defmodule Cannery.Mailer do
|
defmodule Cannery.Mailer do
|
||||||
|
@moduledoc """
|
||||||
|
Mailer adapter for emails
|
||||||
|
"""
|
||||||
|
|
||||||
use Swoosh.Mailer, otp_app: :cannery
|
use Swoosh.Mailer, otp_app: :cannery
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
defmodule Cannery.Release do
|
defmodule Cannery.Release do
|
||||||
|
@moduledoc """
|
||||||
|
Contains tasks that will be included in the Mix release
|
||||||
|
|
||||||
|
Ex. `load_app/0` can be invoked with `mix load_app`.
|
||||||
|
"""
|
||||||
|
|
||||||
@app :cannery
|
@app :cannery
|
||||||
|
|
||||||
def rollback(repo, version) do
|
def rollback(repo, version) do
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule Cannery.Repo.Migrator do
|
defmodule Cannery.Repo.Migrator do
|
||||||
|
@moduledoc """
|
||||||
|
Genserver to automatically run migrations in prod env
|
||||||
|
"""
|
||||||
|
|
||||||
use GenServer
|
use GenServer
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
defmodule Cannery.Tags.Tag do
|
defmodule Cannery.Tags.Tag do
|
||||||
|
@moduledoc """
|
||||||
|
Tags are added to containers to help organize, and can include custom-defined
|
||||||
|
text and bg colors.
|
||||||
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Cannery.{Accounts}
|
alias Ecto.{Changeset, UUID}
|
||||||
|
alias Cannery.{Accounts.User, Tags.Tag}
|
||||||
|
|
||||||
@primary_key {:id, :binary_id, autogenerate: true}
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
@foreign_key_type :binary_id
|
@foreign_key_type :binary_id
|
||||||
@ -10,23 +16,26 @@ defmodule Cannery.Tags.Tag do
|
|||||||
field :bg_color, :string
|
field :bg_color, :string
|
||||||
field :text_color, :string
|
field :text_color, :string
|
||||||
|
|
||||||
belongs_to :user, Accounts.User
|
belongs_to :user, User
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@type t :: %{
|
@type t :: %Tag{
|
||||||
id: Ecto.UUID.t(),
|
id: UUID.t(),
|
||||||
name: String.t(),
|
name: String.t(),
|
||||||
bg_color: String.t(),
|
bg_color: String.t(),
|
||||||
text_color: String.t(),
|
text_color: String.t(),
|
||||||
user: Accounts.User.t(),
|
user: User.t(),
|
||||||
user_id: Ecto.UUID.t(),
|
user_id: UUID.t(),
|
||||||
inserted_at: NaiveDateTime.t(),
|
inserted_at: NaiveDateTime.t(),
|
||||||
updated_at: NaiveDateTime.t()
|
updated_at: NaiveDateTime.t()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@type new_tag() :: %Tag{}
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@spec changeset(Tag.t() | Tag.new_tag(), map()) :: Changeset.t()
|
||||||
def changeset(tag, attrs) do
|
def changeset(tag, attrs) do
|
||||||
tag
|
tag
|
||||||
|> cast(attrs, [:name, :bg_color, :text_color, :user_id])
|
|> cast(attrs, [:name, :bg_color, :text_color, :user_id])
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
defmodule CanneryWeb.Component.Topbar do
|
defmodule CanneryWeb.Component.Topbar do
|
||||||
|
@moduledoc """
|
||||||
|
Component that renders a topbar with user functions/links
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :component
|
use CanneryWeb, :component
|
||||||
|
|
||||||
alias Cannery.{Accounts}
|
alias Cannery.Accounts
|
||||||
alias CanneryWeb.{HomeLive}
|
alias CanneryWeb.HomeLive
|
||||||
|
|
||||||
def topbar(assigns) do
|
def topbar(assigns) do
|
||||||
assigns =
|
assigns =
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
defmodule CanneryWeb.UserAuth do
|
defmodule CanneryWeb.UserAuth do
|
||||||
|
@moduledoc """
|
||||||
|
Functions for user session and authentication
|
||||||
|
"""
|
||||||
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
import Phoenix.Controller
|
import Phoenix.Controller
|
||||||
|
|
||||||
alias Cannery.Accounts
|
alias Cannery.Accounts
|
||||||
alias CanneryWeb.{HomeLive}
|
alias CanneryWeb.HomeLive
|
||||||
alias CanneryWeb.Router.Helpers, as: Routes
|
alias CanneryWeb.Router.Helpers, as: Routes
|
||||||
|
|
||||||
# Make the remember me cookie valid for 60 days.
|
# Make the remember me cookie valid for 60 days.
|
||||||
@ -37,6 +41,11 @@ defmodule CanneryWeb.UserAuth do
|
|||||||
|> redirect(to: user_return_to || signed_in_path(conn))
|
|> redirect(to: user_return_to || signed_in_path(conn))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec maybe_write_remember_me_cookie(
|
||||||
|
Plug.Conn.t(),
|
||||||
|
String.t() | any(),
|
||||||
|
%{required(String.t()) => String.t()} | any()
|
||||||
|
) :: Plug.Conn.t()
|
||||||
defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do
|
defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do
|
||||||
put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options)
|
put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options)
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.AmmoGroupLive.FormComponent do
|
defmodule CanneryWeb.AmmoGroupLive.FormComponent do
|
||||||
|
@moduledoc """
|
||||||
|
Livecomponent that can update or create an Cannery.Ammo.AmmoGroup
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_component
|
use CanneryWeb, :live_component
|
||||||
|
|
||||||
alias Cannery.Ammo
|
alias Cannery.Ammo
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.AmmoGroupLive.Index do
|
defmodule CanneryWeb.AmmoGroupLive.Index do
|
||||||
|
@moduledoc """
|
||||||
|
Liveview to show a Cannery.Ammo.AmmoGroup index
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_view
|
use CanneryWeb, :live_view
|
||||||
|
|
||||||
alias Cannery.Ammo
|
alias Cannery.Ammo
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.AmmoGroupLive.Show do
|
defmodule CanneryWeb.AmmoGroupLive.Show do
|
||||||
|
@moduledoc """
|
||||||
|
Liveview for showing and editing an Cannery.Ammo.AmmoGroup
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_view
|
use CanneryWeb, :live_view
|
||||||
|
|
||||||
alias Cannery.Ammo
|
alias Cannery.Ammo
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.AmmoTypeLive.FormComponent do
|
defmodule CanneryWeb.AmmoTypeLive.FormComponent do
|
||||||
|
@moduledoc """
|
||||||
|
Livecomponent that can update or create an Cannery.Ammo.AmmoType
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_component
|
use CanneryWeb, :live_component
|
||||||
|
|
||||||
alias Cannery.Ammo
|
alias Cannery.Ammo
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.AmmoTypeLive.Index do
|
defmodule CanneryWeb.AmmoTypeLive.Index do
|
||||||
|
@moduledoc """
|
||||||
|
Liveview for showing a Cannery.Ammo.AmmoType index
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_view
|
use CanneryWeb, :live_view
|
||||||
|
|
||||||
alias Cannery.Ammo
|
alias Cannery.Ammo
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.AmmoTypeLive.Show do
|
defmodule CanneryWeb.AmmoTypeLive.Show do
|
||||||
|
@moduledoc """
|
||||||
|
Liveview for showing and editing an Cannery.Ammo.AmmoType
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_view
|
use CanneryWeb, :live_view
|
||||||
|
|
||||||
alias Cannery.Ammo
|
alias Cannery.Ammo
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.ContainerLive.FormComponent do
|
defmodule CanneryWeb.ContainerLive.FormComponent do
|
||||||
|
@moduledoc """
|
||||||
|
Livecomponent that can update or create an Cannery.Containers.Container
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_component
|
use CanneryWeb, :live_component
|
||||||
|
|
||||||
alias Cannery.Containers
|
alias Cannery.Containers
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.ContainerLive.Index do
|
defmodule CanneryWeb.ContainerLive.Index do
|
||||||
|
@moduledoc """
|
||||||
|
Liveview for showing Cannery.Containers.Container index
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_view
|
use CanneryWeb, :live_view
|
||||||
|
|
||||||
alias Cannery.Containers
|
alias Cannery.Containers
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.ContainerLive.Show do
|
defmodule CanneryWeb.ContainerLive.Show do
|
||||||
|
@moduledoc """
|
||||||
|
Liveview for showing and editing a Cannery.Containers.Container
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_view
|
use CanneryWeb, :live_view
|
||||||
|
|
||||||
alias Cannery.Containers
|
alias Cannery.Containers
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
defmodule CanneryWeb.HomeLive do
|
defmodule CanneryWeb.HomeLive do
|
||||||
|
@moduledoc """
|
||||||
|
Liveview for the home page
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_view
|
use CanneryWeb, :live_view
|
||||||
alias Cannery.{Accounts}
|
alias Cannery.Accounts
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, session, socket) do
|
def mount(_params, session, socket) do
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.InviteLive.FormComponent do
|
defmodule CanneryWeb.InviteLive.FormComponent do
|
||||||
|
@moduledoc """
|
||||||
|
Livecomponent that can update or create an Cannery.Invites.Invite
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_component
|
use CanneryWeb, :live_component
|
||||||
|
|
||||||
alias Cannery.Invites
|
alias Cannery.Invites
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
defmodule CanneryWeb.InviteLive.Index do
|
defmodule CanneryWeb.InviteLive.Index do
|
||||||
|
@moduledoc """
|
||||||
|
Liveview to show a Cannery.Invites.Invite index
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_view
|
use CanneryWeb, :live_view
|
||||||
|
|
||||||
alias Cannery.{Invites}
|
alias Cannery.Invites
|
||||||
alias Cannery.Invites.{Invite}
|
alias Cannery.Invites.Invite
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, session, socket) do
|
def mount(_params, session, socket) do
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
defmodule CanneryWeb.LiveHelpers do
|
defmodule CanneryWeb.LiveHelpers do
|
||||||
|
@moduledoc """
|
||||||
|
Contains common helper functions for liveviews
|
||||||
|
"""
|
||||||
|
|
||||||
import Phoenix.LiveView.Helpers
|
import Phoenix.LiveView.Helpers
|
||||||
import Phoenix.LiveView, only: [assign_new: 3]
|
import Phoenix.LiveView, only: [assign_new: 3]
|
||||||
alias Cannery.{Accounts}
|
alias Cannery.Accounts
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders a component inside the `CanneryWeb.ModalComponent` component.
|
Renders a component inside the `CanneryWeb.ModalComponent` component.
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.ModalComponent do
|
defmodule CanneryWeb.ModalComponent do
|
||||||
|
@moduledoc """
|
||||||
|
Livecomponent that displays a floating modal window
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_component
|
use CanneryWeb, :live_component
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.TagLive.FormComponent do
|
defmodule CanneryWeb.TagLive.FormComponent do
|
||||||
|
@moduledoc """
|
||||||
|
Livecomponent that can update or create an Cannery.Tags.Tag
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_component
|
use CanneryWeb, :live_component
|
||||||
|
|
||||||
alias Cannery.Tags
|
alias Cannery.Tags
|
||||||
@ -103,7 +107,7 @@ defmodule CanneryWeb.TagLive.FormComponent do
|
|||||||
Returns a random tag color in `#ffffff` hex format
|
Returns a random tag color in `#ffffff` hex format
|
||||||
"""
|
"""
|
||||||
@spec random_color() :: String.t()
|
@spec random_color() :: String.t()
|
||||||
def random_color() do
|
def random_color do
|
||||||
["#cc0066", "#ff6699", "#6666ff", "#0066cc", "#00cc66", "#669900", "#ff9900", "#996633"]
|
["#cc0066", "#ff6699", "#6666ff", "#0066cc", "#00cc66", "#669900", "#ff9900", "#996633"]
|
||||||
|> Enum.random()
|
|> Enum.random()
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.TagLive.Index do
|
defmodule CanneryWeb.TagLive.Index do
|
||||||
|
@moduledoc """
|
||||||
|
Liveview to show a Cannery.Tags.Tag index
|
||||||
|
"""
|
||||||
|
|
||||||
use CanneryWeb, :live_view
|
use CanneryWeb, :live_view
|
||||||
|
|
||||||
alias Cannery.Tags
|
alias Cannery.Tags
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
defmodule CanneryWeb.Telemetry do
|
defmodule CanneryWeb.Telemetry do
|
||||||
|
@moduledoc """
|
||||||
|
Collects telemetry
|
||||||
|
"""
|
||||||
|
|
||||||
use Supervisor
|
use Supervisor
|
||||||
import Telemetry.Metrics
|
import Telemetry.Metrics
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
defmodule CanneryWeb.LayoutView do
|
defmodule CanneryWeb.LayoutView do
|
||||||
use CanneryWeb, :view
|
use CanneryWeb, :view
|
||||||
alias Cannery.{Accounts}
|
alias Cannery.Accounts
|
||||||
alias CanneryWeb.{HomeLive}
|
alias CanneryWeb.HomeLive
|
||||||
|
|
||||||
# Phoenix LiveDashboard is available only in development by default,
|
# Phoenix LiveDashboard is available only in development by default,
|
||||||
# so we instruct Elixir to not warn if the dashboard route is missing.
|
# so we instruct Elixir to not warn if the dashboard route is missing.
|
||||||
|
@ -16,6 +16,7 @@ defmodule CanneryWeb.ChannelCase do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
use ExUnit.CaseTemplate
|
use ExUnit.CaseTemplate
|
||||||
|
alias Ecto.Adapters.SQL.Sandbox
|
||||||
|
|
||||||
using do
|
using do
|
||||||
quote do
|
quote do
|
||||||
@ -29,8 +30,8 @@ defmodule CanneryWeb.ChannelCase do
|
|||||||
end
|
end
|
||||||
|
|
||||||
setup tags do
|
setup tags do
|
||||||
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
|
pid = Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
|
||||||
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
|
on_exit(fn -> Sandbox.stop_owner(pid) end)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -16,6 +16,7 @@ defmodule CanneryWeb.ConnCase do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
use ExUnit.CaseTemplate
|
use ExUnit.CaseTemplate
|
||||||
|
alias Ecto.Adapters.SQL.Sandbox
|
||||||
|
|
||||||
using do
|
using do
|
||||||
quote do
|
quote do
|
||||||
@ -32,8 +33,8 @@ defmodule CanneryWeb.ConnCase do
|
|||||||
end
|
end
|
||||||
|
|
||||||
setup tags do
|
setup tags do
|
||||||
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
|
pid = Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
|
||||||
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
|
on_exit(fn -> Sandbox.stop_owner(pid) end)
|
||||||
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ defmodule Cannery.DataCase do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
use ExUnit.CaseTemplate
|
use ExUnit.CaseTemplate
|
||||||
|
alias Ecto.Adapters.SQL.Sandbox
|
||||||
|
|
||||||
using do
|
using do
|
||||||
quote do
|
quote do
|
||||||
@ -28,8 +29,8 @@ defmodule Cannery.DataCase do
|
|||||||
end
|
end
|
||||||
|
|
||||||
setup tags do
|
setup tags do
|
||||||
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
|
pid = Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
|
||||||
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
|
on_exit(fn -> Sandbox.stop_owner(pid) end)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ defmodule Cannery.AccountsFixtures do
|
|||||||
entities via the `Cannery.Accounts` context.
|
entities via the `Cannery.Accounts` context.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Cannery.{Accounts}
|
alias Cannery.Accounts
|
||||||
|
|
||||||
def unique_user_email, do: "user#{System.unique_integer()}@example.com"
|
def unique_user_email, do: "user#{System.unique_integer()}@example.com"
|
||||||
def valid_user_password, do: "hello world!"
|
def valid_user_password, do: "hello world!"
|
||||||
|
Loading…
Reference in New Issue
Block a user