typespec out models

This commit is contained in:
shibao 2022-01-31 20:08:01 -05:00
parent 7e67f8de7b
commit 616b20e62b
8 changed files with 91 additions and 45 deletions

View File

@ -24,7 +24,7 @@ defmodule Cannery.Accounts.User do
end end
@type t :: %User{ @type t :: %User{
id: UUID.t(), id: id(),
email: String.t(), email: String.t(),
password: String.t(), password: String.t(),
hashed_password: String.t(), hashed_password: String.t(),
@ -34,8 +34,8 @@ defmodule Cannery.Accounts.User do
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_user :: %User{} @type new_user :: %User{}
@type id :: UUID.t()
@doc """ @doc """
A user changeset for registration. A user changeset for registration.
@ -54,8 +54,9 @@ 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() | User.new_user(), map()) :: Changeset.t() @spec registration_changeset(t() | new_user(), attrs :: map()) :: Changeset.t()
@spec registration_changeset(User.t() | User.new_user(), map(), keyword()) :: Changeset.t() @spec registration_changeset(t() | new_user(), attrs :: map(), opts :: 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])
@ -67,7 +68,7 @@ defmodule Cannery.Accounts.User do
A user changeset for role. A user changeset for role.
""" """
@spec role_changeset(User.t(), atom()) :: Changeset.t() @spec role_changeset(t(), role :: 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
@ -82,7 +83,7 @@ defmodule Cannery.Accounts.User do
|> unique_constraint(:email) |> unique_constraint(:email)
end end
@spec validate_password(Changeset.t(), keyword()) :: Changeset.t() @spec validate_password(Changeset.t(), opts :: keyword()) :: Changeset.t()
defp validate_password(changeset, opts) do defp validate_password(changeset, opts) do
changeset changeset
|> validate_required([:password]) |> validate_required([:password])
@ -93,7 +94,7 @@ defmodule Cannery.Accounts.User do
|> maybe_hash_password(opts) |> maybe_hash_password(opts)
end end
@spec maybe_hash_password(Changeset.t(), keyword()) :: Changeset.t() @spec maybe_hash_password(Changeset.t(), opts :: 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)
@ -112,7 +113,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()) :: Changeset.t() @spec email_changeset(t(), attrs :: map()) :: Changeset.t()
def email_changeset(user, attrs) do def email_changeset(user, attrs) do
user user
|> cast(attrs, [:email]) |> cast(attrs, [:email])
@ -135,8 +136,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()) :: Changeset.t() @spec password_changeset(t(), attrs :: map()) :: Changeset.t()
@spec password_changeset(User.t(), map(), keyword()) :: Changeset.t() @spec password_changeset(t(), attrs :: map(), opts :: 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])
@ -147,7 +148,7 @@ 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() | Changeset.t()) :: Changeset.t() @spec confirm_changeset(t() | Changeset.t()) :: Changeset.t()
def confirm_changeset(user_or_changeset) do def confirm_changeset(user_or_changeset) do
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
user_or_changeset |> change(confirmed_at: now) user_or_changeset |> change(confirmed_at: now)
@ -159,7 +160,7 @@ defmodule Cannery.Accounts.User do
If there is no user or the user doesn't have a password, we call If there is no user or the user doesn't have a password, we call
`Bcrypt.no_user_verify/0` to avoid timing attacks. `Bcrypt.no_user_verify/0` to avoid timing attacks.
""" """
@spec valid_password?(User.t(), String.t()) :: boolean() @spec valid_password?(t(), String.t()) :: boolean()
def valid_password?(%User{hashed_password: hashed_password}, password) def valid_password?(%User{hashed_password: hashed_password}, password)
when is_binary(hashed_password) and byte_size(password) > 0 do when is_binary(hashed_password) and byte_size(password) > 0 do
Bcrypt.verify_pass(password, hashed_password) Bcrypt.verify_pass(password, hashed_password)

View File

@ -31,23 +31,23 @@ defmodule Cannery.Accounts.UserToken do
end end
@type t :: %UserToken{ @type t :: %UserToken{
id: UUID.t(), id: id(),
token: String.t(), token: String.t(),
context: String.t(), context: String.t(),
sent_to: String.t(), sent_to: String.t(),
user: User.t(), user: User.t(),
user_id: UUID.t(), user_id: User.id(),
inserted_at: NaiveDateTime.t() inserted_at: NaiveDateTime.t()
} }
@type new_token :: %UserToken{} @type new_token :: %UserToken{}
@type id :: UUID.t()
@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.
""" """
@spec build_session_token(User.t()) :: {token :: String.t(), UserToken.new_token()} @spec build_session_token(User.t()) :: {token :: String.t(), new_token()}
def build_session_token(%{id: user_id}) do def build_session_token(%{id: user_id}) do
token = :crypto.strong_rand_bytes(@rand_size) token = :crypto.strong_rand_bytes(@rand_size)
{token, %UserToken{token: token, context: "session", user_id: user_id}} {token, %UserToken{token: token, context: "session", user_id: user_id}}
@ -58,7 +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()} @spec verify_session_token_query(token :: 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"),
@ -77,13 +77,13 @@ 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()} @spec build_email_token(User.t(), context :: String.t()) :: {token :: String.t(), 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()) :: @spec build_hashed_token(User.t(), String.t(), String.t()) ::
{String.t(), UserToken.new_token()} {String.t(), 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)
@ -102,7 +102,8 @@ 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 @spec verify_email_token_query(token :: String.t(), context :: 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} ->
@ -122,7 +123,7 @@ defmodule Cannery.Accounts.UserToken do
end end
end end
@spec days_for_context(<<_::56>>) :: non_neg_integer() @spec days_for_context(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
@ -131,7 +132,8 @@ 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 @spec verify_change_email_token_query(token :: String.t(), context :: 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} ->
@ -151,8 +153,7 @@ 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(token :: String.t(), context :: 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 UserToken, where: [token: ^token, context: ^context] from UserToken, where: [token: ^token, context: ^context]
end end
@ -160,6 +161,8 @@ defmodule Cannery.Accounts.UserToken do
@doc """ @doc """
Gets all tokens for the given user for the given contexts. Gets all tokens for the given user for the given contexts.
""" """
@spec user_and_contexts_query(User.t(), contexts :: :all | nonempty_maybe_improper_list()) ::
Query.t()
def user_and_contexts_query(%{id: user_id}, :all) do def user_and_contexts_query(%{id: user_id}, :all) do
from t in UserToken, where: t.user_id == ^user_id from t in UserToken, where: t.user_id == ^user_id
end end

View File

@ -8,7 +8,9 @@ defmodule Cannery.Ammo.AmmoGroup do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Cannery.{Accounts, Ammo, Containers, Tags} alias Cannery.Ammo.{AmmoGroup, AmmoType}
alias Cannery.{Accounts.User, Containers.Container, Tags.Tag}
alias Ecto.{Changeset, UUID}
@primary_key {:id, :binary_id, autogenerate: true} @primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id @foreign_key_type :binary_id
@ -17,15 +19,35 @@ defmodule Cannery.Ammo.AmmoGroup do
field :notes, :string field :notes, :string
field :price_paid, :float field :price_paid, :float
belongs_to :tag, Tags.Tag belongs_to :tag, Tag
belongs_to :ammo_type, Ammo.AmmoType belongs_to :ammo_type, AmmoType
belongs_to :container, Containers.Container belongs_to :container, Container
belongs_to :user, Accounts.User belongs_to :user, User
timestamps() timestamps()
end end
@type t :: %AmmoGroup{
id: id(),
count: integer,
notes: String.t(),
price_paid: float(),
tag: Tag.t(),
tag_id: Tag.id(),
ammo_type: AmmoType.t(),
ammo_type_id: AmmoType.id(),
container: Container.t(),
container_id: Container.id(),
user: User.t(),
user_id: User.id(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type new_ammo_group :: %AmmoGroup{}
@type id :: UUID.t()
@doc false @doc false
@spec changeset(t() | new_ammo_group(), attrs :: map()) :: Changeset.t()
def changeset(ammo_group, attrs) do def changeset(ammo_group, attrs) do
ammo_group ammo_group
|> cast(attrs, [:count, :price_paid, :notes, :tag_id, :ammo_type_id, :container_id, :user_id]) |> cast(attrs, [:count, :price_paid, :notes, :tag_id, :ammo_type_id, :container_id, :user_id])

View File

@ -7,6 +7,8 @@ defmodule Cannery.Ammo.AmmoType do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Cannery.Ammo.AmmoType
alias Ecto.{Changeset, UUID}
@primary_key {:id, :binary_id, autogenerate: true} @primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id @foreign_key_type :binary_id
@ -21,7 +23,22 @@ defmodule Cannery.Ammo.AmmoType do
timestamps() timestamps()
end end
@type t :: %AmmoType{
id: id(),
bullet_type: String.t(),
case_material: String.t(),
desc: String.t(),
manufacturer: String.t(),
name: String.t(),
weight: float(),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
}
@type new_ammo_type :: %AmmoType{}
@type id :: UUID.t()
@doc false @doc false
@spec changeset(t() | new_ammo_type(), attrs :: map()) :: Changeset.t()
def changeset(ammo_type, attrs) do def changeset(ammo_type, attrs) do
ammo_type ammo_type
|> cast(attrs, [:name, :desc, :case_material, :bullet_type, :weight, :manufacturer]) |> cast(attrs, [:name, :desc, :case_material, :bullet_type, :weight, :manufacturer])

View File

@ -22,21 +22,21 @@ defmodule Cannery.Containers.Container do
end end
@type t :: %Container{ @type t :: %Container{
id: UUID.t(), id: id(),
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: User.t(), user: User.t(),
user_id: UUID.t(), user_id: User.id(),
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_container :: %Container{} @type new_container :: %Container{}
@type id :: UUID.t()
@doc false @doc false
@spec changeset(Container.t() | Container.new_container(), map()) :: Changeset.t() @spec changeset(t() | new_container(), attrs :: 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])

View File

@ -7,6 +7,7 @@ defmodule Cannery.Containers.ContainerTag do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Cannery.{Containers.Container, Containers.ContainerTag, Tags.Tag} alias Cannery.{Containers.Container, Containers.ContainerTag, Tags.Tag}
alias Ecto.{Changeset, UUID}
@primary_key {:id, :binary_id, autogenerate: true} @primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id @foreign_key_type :binary_id
@ -18,17 +19,19 @@ defmodule Cannery.Containers.ContainerTag do
end end
@type t :: %ContainerTag{ @type t :: %ContainerTag{
id: Ecto.UUID.t(), id: id(),
container: Container.t(), container: Container.t(),
container_id: Ecto.UUID.t(), container_id: Container.id(),
tag: Tag.t(), tag: Tag.t(),
tag_id: Ecto.UUID.t(), tag_id: Tag.id(),
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_container_tag :: %ContainerTag{}
@type id :: UUID.t()
@doc false @doc false
@spec changeset(ContainerTag.t(), map()) :: Ecto.Changeset.t() @spec changeset(t() | new_container_tag(), attrs :: map()) :: 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

@ -24,21 +24,21 @@ defmodule Cannery.Invites.Invite do
end end
@type t :: %Invite{ @type t :: %Invite{
id: UUID.t(), id: id(),
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: User.t(), user: User.t(),
user_id: UUID.t(), user_id: User.id(),
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_invite :: %Invite{} @type new_invite :: %Invite{}
@type id :: UUID.t()
@doc false @doc false
@spec changeset(Invite.t() | Invite.new_invite(), map()) :: Changeset.t() @spec changeset(t() | new_invite(), attrs :: 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

@ -22,20 +22,20 @@ defmodule Cannery.Tags.Tag do
end end
@type t :: %Tag{ @type t :: %Tag{
id: UUID.t(), id: id(),
name: String.t(), name: String.t(),
bg_color: String.t(), bg_color: String.t(),
text_color: String.t(), text_color: String.t(),
user: User.t(), user: User.t(),
user_id: UUID.t(), user_id: User.id(),
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_tag() :: %Tag{} @type new_tag() :: %Tag{}
@type id() :: UUID.t()
@doc false @doc false
@spec changeset(Tag.t() | Tag.new_tag(), map()) :: Changeset.t() @spec changeset(t() | new_tag(), attrs :: 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])