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
alias Cannery.{Repo}
alias Cannery.Accounts.{User, UserToken, UserNotifier}
alias Cannery.Repo
alias Cannery.Accounts.{User, UserNotifier, UserToken}
alias Ecto.{Changeset, Multi}
## Database getters
@ -89,10 +90,10 @@ defmodule Cannery.Accounts do
{:ok, %User{}}
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
# if no registered users, make first user an admin
attrs =
@ -104,15 +105,16 @@ defmodule Cannery.Accounts do
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking user changes.
Returns an `%Changeset{}` for tracking user changes.
## Examples
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
User.registration_changeset(user, attrs, hash_password: false)
end
@ -120,29 +122,29 @@ defmodule Cannery.Accounts do
## Settings
@doc """
Returns an `%Ecto.Changeset{}` for changing the user email.
Returns an `%Changeset{}` for changing the user email.
## Examples
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
User.email_changeset(user, attrs)
end
@doc """
Returns an `%Ecto.Changeset{}` for changing the user role.
Returns an `%Changeset{}` for changing the user role.
## Examples
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
User.role_changeset(user, role)
end
@ -157,16 +159,16 @@ defmodule Cannery.Accounts do
{:ok, %User{}}
iex> apply_user_email(user, "invalid password", %{email: ...})
{:error, %Ecto.Changeset{}}
{:error, %Changeset{}}
"""
@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
user
|> User.email_changeset(attrs)
|> User.validate_current_password(password)
|> Ecto.Changeset.apply_action(:update)
|> Changeset.apply_action(:update)
end
@doc """
@ -175,7 +177,7 @@ defmodule Cannery.Accounts do
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.
"""
@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
context = "change:#{user.email}"
@ -188,12 +190,13 @@ defmodule Cannery.Accounts do
end
end
@spec user_email_multi(User.t(), String.t(), String.t()) :: Multi.t()
defp user_email_multi(user, email, context) do
changeset = user |> User.email_changeset(%{email: email}) |> User.confirm_changeset()
Ecto.Multi.new()
|> Ecto.Multi.update(:user, changeset)
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
Multi.new()
|> Multi.update(:user, changeset)
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
end
@doc """
@ -216,15 +219,15 @@ defmodule Cannery.Accounts do
end
@doc """
Returns an `%Ecto.Changeset{}` for changing the user password.
Returns an `%Changeset{}` for changing the user password.
## Examples
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
User.password_changeset(user, attrs, hash_password: false)
end
@ -238,20 +241,20 @@ defmodule Cannery.Accounts do
{:ok, %User{}}
iex> update_user_password(user, "invalid password", %{password: ...})
{:error, %Ecto.Changeset{}}
{:error, %Changeset{}}
"""
@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
changeset =
user
|> User.password_changeset(attrs)
|> User.validate_current_password(password)
Ecto.Multi.new()
|> Ecto.Multi.update(:user, changeset)
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
Multi.new()
|> Multi.update(:user, changeset)
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|> Repo.transaction()
|> case do
{:ok, %{user: user}} -> {:ok, user}
@ -269,7 +272,7 @@ defmodule Cannery.Accounts do
@doc """
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
{token, user_token} = UserToken.build_session_token(user)
Repo.insert!(user_token)
@ -279,7 +282,7 @@ defmodule Cannery.Accounts do
@doc """
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
{:ok, query} = UserToken.verify_session_token_query(token)
Repo.one(query)
@ -288,7 +291,7 @@ defmodule Cannery.Accounts do
@doc """
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
Repo.delete_all(UserToken.token_and_context_query(token, "session"))
:ok
@ -298,7 +301,7 @@ defmodule Cannery.Accounts do
Returns a boolean if registration is allowed or not
"""
@spec allow_registration?() :: boolean()
def allow_registration?() do
def allow_registration? do
Application.get_env(:cannery, CanneryWeb.Endpoint)[:registration] == "public" or
list_users_by_role(:admin) |> Enum.empty?()
end
@ -336,7 +339,7 @@ defmodule Cannery.Accounts do
If the token matches, the user account is marked as confirmed
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
with {:ok, query} <- UserToken.verify_email_token_query(token, "confirm"),
%User{} = user <- Repo.one(query),
@ -348,9 +351,9 @@ defmodule Cannery.Accounts do
end
defp confirm_user_multi(user) do
Ecto.Multi.new()
|> Ecto.Multi.update(:user, User.confirm_changeset(user))
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["confirm"]))
Multi.new()
|> Multi.update(:user, User.confirm_changeset(user))
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["confirm"]))
end
## Reset password
@ -385,7 +388,7 @@ defmodule Cannery.Accounts do
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
with {:ok, query} <- UserToken.verify_email_token_query(token, "reset_password"),
%User{} = user <- Repo.one(query) do
@ -404,14 +407,14 @@ defmodule Cannery.Accounts do
{:ok, %User{}}
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
Ecto.Multi.new()
|> Ecto.Multi.update(:user, User.password_changeset(user, attrs))
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
Multi.new()
|> Multi.update(:user, User.password_changeset(user, attrs))
|> Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|> Repo.transaction()
|> case do
{:ok, %{user: user}} -> {:ok, user}

View File

@ -1,8 +1,12 @@
defmodule Cannery.Accounts.User do
@moduledoc """
A cannery user
"""
use Ecto.Schema
import Ecto.Changeset
alias Cannery.Accounts.{User}
alias Cannery.Invites.{Invite}
alias Ecto.{Changeset, UUID}
alias Cannery.{Accounts.User, Invites.Invite}
@derive {Inspect, except: [:password]}
@primary_key {:id, :binary_id, autogenerate: true}
@ -19,8 +23,8 @@ defmodule Cannery.Accounts.User do
timestamps()
end
@type t :: %{
id: Ecto.UUID.t(),
@type t :: %User{
id: UUID.t(),
email: String.t(),
password: String.t(),
hashed_password: String.t(),
@ -31,6 +35,8 @@ defmodule Cannery.Accounts.User do
updated_at: NaiveDateTime.t()
}
@type new_user :: %User{}
@doc """
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`.
Defaults to `true`.
"""
@spec registration_changeset(User.t(), map()) :: Ecto.Changeset.t()
@spec registration_changeset(User.t(), map(), keyword()) :: Ecto.Changeset.t()
@spec registration_changeset(User.t() | User.new_user(), map()) :: Changeset.t()
@spec registration_changeset(User.t() | User.new_user(), map(), keyword()) :: Changeset.t()
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :password, :role])
@ -61,12 +67,12 @@ defmodule Cannery.Accounts.User do
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
user |> cast(%{"role" => role}, [:role])
end
@spec validate_email(Ecto.Changeset.t()) :: Ecto.Changeset.t()
@spec validate_email(Changeset.t()) :: Changeset.t()
defp validate_email(changeset) do
changeset
|> validate_required([:email])
@ -76,7 +82,7 @@ defmodule Cannery.Accounts.User do
|> unique_constraint(:email)
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
changeset
|> validate_required([:password])
@ -87,7 +93,7 @@ defmodule Cannery.Accounts.User do
|> maybe_hash_password(opts)
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
hash_password? = Keyword.get(opts, :hash_password, true)
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.
"""
@spec email_changeset(User.t(), map()) :: Ecto.Changeset.t()
@spec email_changeset(User.t(), map()) :: Changeset.t()
def email_changeset(user, attrs) do
user
|> cast(attrs, [:email])
@ -129,8 +135,8 @@ defmodule Cannery.Accounts.User do
validations on a LiveView form), this option can be set to `false`.
Defaults to `true`.
"""
@spec password_changeset(User.t(), map()) :: Ecto.Changeset.t()
@spec password_changeset(User.t(), map(), keyword()) :: Ecto.Changeset.t()
@spec password_changeset(User.t(), map()) :: Changeset.t()
@spec password_changeset(User.t(), map(), keyword()) :: Changeset.t()
def password_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:password])
@ -141,10 +147,10 @@ defmodule Cannery.Accounts.User do
@doc """
Confirms the account by setting `confirmed_at`.
"""
@spec confirm_changeset(User.t()) :: Ecto.Changeset.t()
def confirm_changeset(user) do
@spec confirm_changeset(User.t() | Changeset.t()) :: Changeset.t()
def confirm_changeset(user_or_changeset) do
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
change(user, confirmed_at: now)
user_or_changeset |> change(confirmed_at: now)
end
@doc """
@ -167,12 +173,10 @@ defmodule Cannery.Accounts.User do
@doc """
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
if valid_password?(changeset.data, password) do
changeset
else
add_error(changeset, :current_password, "is not valid")
end
if valid_password?(changeset.data, password),
do: changeset,
else: changeset |> add_error(:current_password, "is not valid")
end
end

View File

@ -1,4 +1,8 @@
defmodule Cannery.Accounts.UserNotifier do
@moduledoc """
Contains all user emails and notifications
"""
# For simplicity, this module simply logs messages to the terminal.
# You should replace it by a proper email or notification tool, such as:
#

View File

@ -1,7 +1,12 @@
defmodule Cannery.Accounts.UserToken do
@moduledoc """
Schema for serialized user session and authentication tokens
"""
use Ecto.Schema
import Ecto.Query
alias Cannery.{Accounts}
alias Ecto.{Query, UUID}
alias Cannery.{Accounts.User, Accounts.UserToken}
@hash_algorithm :sha256
@rand_size 32
@ -19,19 +24,33 @@ defmodule Cannery.Accounts.UserToken do
field :token, :binary
field :context, :string
field :sent_to, :string
belongs_to :user, Accounts.User
belongs_to :user, User
timestamps(updated_at: false)
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 """
Generates a token that will be stored in a signed place,
such as session or cookie. As they are signed, those
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, %Accounts.UserToken{token: token, context: "session", user_id: user.id}}
{token, %UserToken{token: token, context: "session", user_id: user_id}}
end
@doc """
@ -39,6 +58,7 @@ defmodule Cannery.Accounts.UserToken do
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
query =
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
their email.
"""
@spec build_email_token(User.t(), String.t()) :: {String.t(), UserToken.new_token()}
def build_email_token(user, context) do
build_hashed_token(user, context, user.email)
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
token = :crypto.strong_rand_bytes(@rand_size)
hashed_token = :crypto.hash(@hash_algorithm, token)
{Base.url_encode64(token, padding: false),
%Accounts.UserToken{
%UserToken{
token: hashed_token,
context: context,
sent_to: sent_to,
@ -79,6 +102,7 @@ defmodule Cannery.Accounts.UserToken do
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
case Base.url_decode64(token, padding: false) do
{:ok, decoded_token} ->
@ -98,6 +122,7 @@ defmodule Cannery.Accounts.UserToken do
end
end
@spec days_for_context(<<_::56>>) :: non_neg_integer()
defp days_for_context("confirm"), do: @confirm_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.
"""
@spec verify_change_email_token_query(String.t(), String.t()) :: {:ok, Query.t()} | :error
def verify_change_email_token_query(token, context) do
case Base.url_decode64(token, padding: false) do
{:ok, decoded_token} ->
@ -125,18 +151,20 @@ defmodule Cannery.Accounts.UserToken do
@doc """
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
from Accounts.UserToken, where: [token: ^token, context: ^context]
from UserToken, where: [token: ^token, context: ^context]
end
@doc """
Gets all tokens for the given user for the given contexts.
"""
def user_and_contexts_query(user, :all) do
from t in Accounts.UserToken, where: t.user_id == ^user.id
def user_and_contexts_query(%{id: user_id}, :all) do
from t in UserToken, where: t.user_id == ^user_id
end
def user_and_contexts_query(user, [_ | _] = contexts) do
from t in Accounts.UserToken, where: t.user_id == ^user.id and t.context in ^contexts
def user_and_contexts_query(%{id: user_id}, [_ | _] = contexts) do
from t in UserToken, where: t.user_id == ^user_id and t.context in ^contexts
end
end

View File

@ -1,4 +1,11 @@
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
import Ecto.Changeset
alias Cannery.{Accounts, Ammo, Containers, Tags}

View File

@ -1,4 +1,10 @@
defmodule Cannery.Ammo.AmmoType do
@moduledoc """
An ammunition type.
Contains statistical information about the ammunition.
"""
use Ecto.Schema
import Ecto.Changeset

View File

@ -4,9 +4,8 @@ defmodule Cannery.Containers do
"""
import Ecto.Query, warn: false
alias Cannery.Repo
alias Cannery.Containers.Container
alias Cannery.{Containers.Container, Repo}
alias Ecto.{Changeset, UUID}
@doc """
Returns the list of containers.
@ -17,6 +16,7 @@ defmodule Cannery.Containers do
[%Container{}, ...]
"""
@spec list_containers() :: [Container.t()]
def list_containers do
Repo.all(Container)
end
@ -35,6 +35,7 @@ defmodule Cannery.Containers do
** (Ecto.NoResultsError)
"""
@spec get_container!(container_id :: UUID.t()) :: Container.t()
def get_container!(id), do: Repo.get!(Container, id)
@doc """
@ -49,10 +50,9 @@ defmodule Cannery.Containers do
{:error, %Ecto.Changeset{}}
"""
def create_container(attrs \\ %{}) do
%Container{}
|> Container.changeset(attrs)
|> Repo.insert()
@spec create_container(attrs :: map()) :: {:ok, Container.t()} | {:error, Changeset.t()}
def create_container(attrs) do
%Container{} |> Container.changeset(attrs) |> Repo.insert()
end
@doc """
@ -67,10 +67,10 @@ defmodule Cannery.Containers do
{:error, %Ecto.Changeset{}}
"""
def update_container(%Container{} = container, attrs) do
container
|> Container.changeset(attrs)
|> Repo.update()
@spec update_container(Container.t() | Ecto.Changeset.t(), map()) ::
{:ok, Container.t()} | {:error, Ecto.Changeset.t()}
def update_container(container, attrs) do
container |> Container.changeset(attrs) |> Repo.update()
end
@doc """
@ -85,9 +85,9 @@ defmodule Cannery.Containers do
{:error, %Ecto.Changeset{}}
"""
def delete_container(%Container{} = container) do
Repo.delete(container)
end
@spec delete_container(Container.t() | Ecto.Changeset.t()) ::
{:ok, Container.t()} | {:error, Ecto.Changeset.t()}
def delete_container(container), do: Repo.delete(container)
@doc """
Returns an `%Ecto.Changeset{}` for tracking container changes.
@ -97,8 +97,11 @@ defmodule Cannery.Containers do
iex> change_container(container)
%Ecto.Changeset{data: %Container{}}
iex> change_container(%Ecto.Changeset{})
%Ecto.Changeset{data: %Container{}}
"""
def change_container(%Container{} = container, attrs \\ %{}) do
Container.changeset(container, attrs)
end
@spec change_container(Container.t()) :: Changeset.t()
@spec change_container(Container.t(), map()) :: Changeset.t()
def change_container(container, attrs \\ %{}), do: container |> Container.changeset(attrs)
end

View File

@ -1,7 +1,12 @@
defmodule Cannery.Containers.Container do
@moduledoc """
A container that holds ammunition and belongs to a user.
"""
use Ecto.Schema
import Ecto.Changeset
alias Cannery.{Accounts}
alias Ecto.{Changeset, UUID}
alias Cannery.{Accounts.User, Containers.Container}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@ -11,27 +16,30 @@ defmodule Cannery.Containers.Container do
field :location, :string
field :type, :string
belongs_to :user, Accounts.User
belongs_to :user, User
timestamps()
end
@type t :: %{
id: Ecto.UUID.t(),
@type t :: %Container{
id: UUID.t(),
name: String.t(),
desc: String.t(),
location: String.t(),
type: String.t(),
user: Accounts.User.t(),
user_id: Ecto.UUID.t(),
user: User.t(),
user_id: UUID.t(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type new_container :: %Container{}
@doc false
@spec changeset(Container.t() | Container.new_container(), map()) :: Changeset.t()
def changeset(container, attrs) do
container
|> cast(attrs, [:name, :desc, :type, :location, :user_id])
|> validate_required([:name, :desc, :type, :location, :user_id])
|> validate_required([:name, :type, :user_id])
end
end

View File

@ -1,28 +1,34 @@
defmodule Cannery.Containers.ContainerTag do
@moduledoc """
Thru-table struct for associating Cannery.Containers.Container and
Cannery.Tags.Tag.
"""
use Ecto.Schema
import Ecto.Changeset
alias Cannery.{Containers, Tags}
alias Cannery.{Containers.Container, Containers.ContainerTag, Tags.Tag}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "container_tags" do
belongs_to :container, Containers.Container
belongs_to :tag, Tags.Tag
belongs_to :container, Container
belongs_to :tag, Tag
timestamps()
end
@type t :: %{
@type t :: %ContainerTag{
id: Ecto.UUID.t(),
container: Containers.Container.t(),
container: Container.t(),
container_id: Ecto.UUID.t(),
tag: Tags.Tag.t(),
tag: Tag.t(),
tag_id: Ecto.UUID.t(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@doc false
@spec changeset(ContainerTag.t(), map()) :: Ecto.Changeset.t()
def changeset(container_tag, attrs) do
container_tag
|> cast(attrs, [:tag_id, :container_id])

View File

@ -4,8 +4,8 @@ defmodule Cannery.Invites do
"""
import Ecto.Query, warn: false
alias Cannery.{Accounts, Repo}
alias Cannery.Invites.Invite
alias Ecto.{Changeset, UUID}
alias Cannery.{Accounts.User, Invites.Invite, Repo}
@invite_token_length 20
@ -49,7 +49,7 @@ defmodule Cannery.Invites do
iex> get_invite_by_token("invalid_token")
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(""), do: nil
@ -86,22 +86,22 @@ defmodule Cannery.Invites do
## Examples
iex> create_invite(%Accounts.User{id: "1"}, %{field: value})
iex> create_invite(%User{id: "1"}, %{field: value})
{:ok, %Invite{}}
iex> create_invite("1", %{field: value})
{:ok, %Invite{}}
iex> create_invite(%Accounts.User{id: "1"}, %{field: bad_value})
iex> create_invite(%User{id: "1"}, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
@spec create_invite(user_or_user_id :: Accounts.User.t() | Ecto.UUID.t(), attrs :: map()) ::
Invite.t()
def create_invite(%{id: user_id}, attrs) do
create_invite(user_id, attrs)
end
@spec create_invite(user :: User.t(), attrs :: map()) ::
{:ok, Invite.t()} | {:error, Changeset.t()}
def create_invite(%{id: user_id}, attrs), do: create_invite(user_id, attrs)
@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
attrs =
attrs

View File

@ -1,7 +1,14 @@
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
import Ecto.Changeset
alias Cannery.{Accounts}
alias Ecto.{Changeset, UUID}
alias Cannery.{Accounts.User, Invites.Invite}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@ -11,22 +18,27 @@ defmodule Cannery.Invites.Invite do
field :uses_left, :integer, default: nil
field :disabled_at, :naive_datetime
belongs_to :user, Accounts.User
belongs_to :user, User
timestamps()
end
@type t :: %{
id: Ecto.UUID.t(),
@type t :: %Invite{
id: UUID.t(),
name: String.t(),
token: String.t(),
uses_left: integer() | nil,
disabled_at: NaiveDateTime.t(),
user: Accounts.User.t(),
user_id: Ecto.UUID.t()
user: User.t(),
user_id: UUID.t(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type new_invite :: %Invite{}
@doc false
@spec changeset(Invite.t() | Invite.new_invite(), map()) :: Changeset.t()
def changeset(invite, attrs) do
invite
|> cast(attrs, [:name, :token, :uses_left, :disabled_at, :user_id])

View File

@ -1,3 +1,7 @@
defmodule Cannery.Mailer do
@moduledoc """
Mailer adapter for emails
"""
use Swoosh.Mailer, otp_app: :cannery
end

View File

@ -1,4 +1,10 @@
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
def rollback(repo, version) do

View File

@ -1,4 +1,8 @@
defmodule Cannery.Repo.Migrator do
@moduledoc """
Genserver to automatically run migrations in prod env
"""
use GenServer
require Logger

View File

@ -1,7 +1,13 @@
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
import Ecto.Changeset
alias Cannery.{Accounts}
alias Ecto.{Changeset, UUID}
alias Cannery.{Accounts.User, Tags.Tag}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@ -10,23 +16,26 @@ defmodule Cannery.Tags.Tag do
field :bg_color, :string
field :text_color, :string
belongs_to :user, Accounts.User
belongs_to :user, User
timestamps()
end
@type t :: %{
id: Ecto.UUID.t(),
@type t :: %Tag{
id: UUID.t(),
name: String.t(),
bg_color: String.t(),
text_color: String.t(),
user: Accounts.User.t(),
user_id: Ecto.UUID.t(),
user: User.t(),
user_id: UUID.t(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type new_tag() :: %Tag{}
@doc false
@spec changeset(Tag.t() | Tag.new_tag(), map()) :: Changeset.t()
def changeset(tag, attrs) do
tag
|> cast(attrs, [:name, :bg_color, :text_color, :user_id])

View File

@ -1,8 +1,12 @@
defmodule CanneryWeb.Component.Topbar do
@moduledoc """
Component that renders a topbar with user functions/links
"""
use CanneryWeb, :component
alias Cannery.{Accounts}
alias CanneryWeb.{HomeLive}
alias Cannery.Accounts
alias CanneryWeb.HomeLive
def topbar(assigns) do
assigns =

View File

@ -1,9 +1,13 @@
defmodule CanneryWeb.UserAuth do
@moduledoc """
Functions for user session and authentication
"""
import Plug.Conn
import Phoenix.Controller
alias Cannery.Accounts
alias CanneryWeb.{HomeLive}
alias CanneryWeb.HomeLive
alias CanneryWeb.Router.Helpers, as: Routes
# 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))
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
put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options)
end

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.AmmoGroupLive.FormComponent do
@moduledoc """
Livecomponent that can update or create an Cannery.Ammo.AmmoGroup
"""
use CanneryWeb, :live_component
alias Cannery.Ammo

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.AmmoGroupLive.Index do
@moduledoc """
Liveview to show a Cannery.Ammo.AmmoGroup index
"""
use CanneryWeb, :live_view
alias Cannery.Ammo

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.AmmoGroupLive.Show do
@moduledoc """
Liveview for showing and editing an Cannery.Ammo.AmmoGroup
"""
use CanneryWeb, :live_view
alias Cannery.Ammo

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.AmmoTypeLive.FormComponent do
@moduledoc """
Livecomponent that can update or create an Cannery.Ammo.AmmoType
"""
use CanneryWeb, :live_component
alias Cannery.Ammo

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.AmmoTypeLive.Index do
@moduledoc """
Liveview for showing a Cannery.Ammo.AmmoType index
"""
use CanneryWeb, :live_view
alias Cannery.Ammo

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.AmmoTypeLive.Show do
@moduledoc """
Liveview for showing and editing an Cannery.Ammo.AmmoType
"""
use CanneryWeb, :live_view
alias Cannery.Ammo

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.ContainerLive.FormComponent do
@moduledoc """
Livecomponent that can update or create an Cannery.Containers.Container
"""
use CanneryWeb, :live_component
alias Cannery.Containers

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.ContainerLive.Index do
@moduledoc """
Liveview for showing Cannery.Containers.Container index
"""
use CanneryWeb, :live_view
alias Cannery.Containers

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.ContainerLive.Show do
@moduledoc """
Liveview for showing and editing a Cannery.Containers.Container
"""
use CanneryWeb, :live_view
alias Cannery.Containers

View File

@ -1,6 +1,10 @@
defmodule CanneryWeb.HomeLive do
@moduledoc """
Liveview for the home page
"""
use CanneryWeb, :live_view
alias Cannery.{Accounts}
alias Cannery.Accounts
@impl true
def mount(_params, session, socket) do

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.InviteLive.FormComponent do
@moduledoc """
Livecomponent that can update or create an Cannery.Invites.Invite
"""
use CanneryWeb, :live_component
alias Cannery.Invites

View File

@ -1,8 +1,12 @@
defmodule CanneryWeb.InviteLive.Index do
@moduledoc """
Liveview to show a Cannery.Invites.Invite index
"""
use CanneryWeb, :live_view
alias Cannery.{Invites}
alias Cannery.Invites.{Invite}
alias Cannery.Invites
alias Cannery.Invites.Invite
@impl true
def mount(_params, session, socket) do

View File

@ -1,7 +1,11 @@
defmodule CanneryWeb.LiveHelpers do
@moduledoc """
Contains common helper functions for liveviews
"""
import Phoenix.LiveView.Helpers
import Phoenix.LiveView, only: [assign_new: 3]
alias Cannery.{Accounts}
alias Cannery.Accounts
@doc """
Renders a component inside the `CanneryWeb.ModalComponent` component.

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.ModalComponent do
@moduledoc """
Livecomponent that displays a floating modal window
"""
use CanneryWeb, :live_component
@impl true

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.TagLive.FormComponent do
@moduledoc """
Livecomponent that can update or create an Cannery.Tags.Tag
"""
use CanneryWeb, :live_component
alias Cannery.Tags
@ -103,7 +107,7 @@ defmodule CanneryWeb.TagLive.FormComponent do
Returns a random tag color in `#ffffff` hex format
"""
@spec random_color() :: String.t()
def random_color() do
def random_color do
["#cc0066", "#ff6699", "#6666ff", "#0066cc", "#00cc66", "#669900", "#ff9900", "#996633"]
|> Enum.random()
end

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.TagLive.Index do
@moduledoc """
Liveview to show a Cannery.Tags.Tag index
"""
use CanneryWeb, :live_view
alias Cannery.Tags

View File

@ -1,4 +1,8 @@
defmodule CanneryWeb.Telemetry do
@moduledoc """
Collects telemetry
"""
use Supervisor
import Telemetry.Metrics

View File

@ -1,7 +1,7 @@
defmodule CanneryWeb.LayoutView do
use CanneryWeb, :view
alias Cannery.{Accounts}
alias CanneryWeb.{HomeLive}
alias Cannery.Accounts
alias CanneryWeb.HomeLive
# Phoenix LiveDashboard is available only in development by default,
# 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
alias Ecto.Adapters.SQL.Sandbox
using do
quote do
@ -29,8 +30,8 @@ defmodule CanneryWeb.ChannelCase do
end
setup tags do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
pid = Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
on_exit(fn -> Sandbox.stop_owner(pid) end)
:ok
end
end

View File

@ -16,6 +16,7 @@ defmodule CanneryWeb.ConnCase do
"""
use ExUnit.CaseTemplate
alias Ecto.Adapters.SQL.Sandbox
using do
quote do
@ -32,8 +33,8 @@ defmodule CanneryWeb.ConnCase do
end
setup tags do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
pid = Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
on_exit(fn -> Sandbox.stop_owner(pid) end)
{:ok, conn: Phoenix.ConnTest.build_conn()}
end

View File

@ -15,6 +15,7 @@ defmodule Cannery.DataCase do
"""
use ExUnit.CaseTemplate
alias Ecto.Adapters.SQL.Sandbox
using do
quote do
@ -28,8 +29,8 @@ defmodule Cannery.DataCase do
end
setup tags do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
pid = Sandbox.start_owner!(Cannery.Repo, shared: not tags[:async])
on_exit(fn -> Sandbox.stop_owner(pid) end)
:ok
end

View File

@ -4,7 +4,7 @@ defmodule Cannery.AccountsFixtures do
entities via the `Cannery.Accounts` context.
"""
alias Cannery.{Accounts}
alias Cannery.Accounts
def unique_user_email, do: "user#{System.unique_integer()}@example.com"
def valid_user_password, do: "hello world!"