fix dialyzer, credo and format

This commit is contained in:
shibao 2022-01-22 21:40:29 -05:00
parent f5b3eb4a5e
commit 04d798aaa7
39 changed files with 331 additions and 143 deletions

View File

@ -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}

View File

@ -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

View File

@ -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:
# #

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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 =

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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!"