improve invites

This commit is contained in:
shibao 2023-02-04 14:14:45 -05:00 committed by oliviasculley
parent 7b60938a75
commit ed8c20e967
19 changed files with 506 additions and 383 deletions

View File

@ -5,7 +5,7 @@ defmodule Lokal.Accounts do
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Lokal.{Mailer, Repo} alias Lokal.{Mailer, Repo}
alias Lokal.Accounts.{User, UserToken} alias Lokal.Accounts.{Invite, Invites, User, UserToken}
alias Ecto.{Changeset, Multi} alias Ecto.{Changeset, Multi}
alias Oban.Job alias Oban.Job
@ -25,7 +25,9 @@ defmodule Lokal.Accounts do
""" """
@spec get_user_by_email(email :: String.t()) :: User.t() | nil @spec get_user_by_email(email :: String.t()) :: User.t() | nil
def get_user_by_email(email) when is_binary(email), do: Repo.get_by(User, email: email) def get_user_by_email(email) when is_binary(email) do
Repo.get_by(User, email: email)
end
@doc """ @doc """
Gets a user by email and password. Gets a user by email and password.
@ -64,7 +66,9 @@ defmodule Lokal.Accounts do
""" """
@spec get_user!(User.t()) :: User.t() @spec get_user!(User.t()) :: User.t()
def get_user!(id), do: Repo.get!(User, id) def get_user!(id) do
Repo.get!(User, id)
end
@doc """ @doc """
Returns all users grouped by role. Returns all users grouped by role.
@ -113,10 +117,18 @@ defmodule Lokal.Accounts do
:passed :passed
""" """
@spec register_user(attrs :: map()) :: {:ok, User.t()} | {:error, User.changeset()} @spec register_user(attrs :: map(), Invite.token() | nil) ::
def register_user(attrs) do {:ok, User.t()} | {:error, :invalid_token | User.changeset()}
def register_user(attrs, invite_token \\ nil) do
Multi.new() Multi.new()
|> Multi.one(:users_count, from(u in User, select: count(u.id), distinct: true)) |> Multi.one(:users_count, from(u in User, select: count(u.id), distinct: true))
|> Multi.run(:use_invite, fn _changes_so_far, _repo ->
if allow_registration?() and invite_token |> is_nil() do
{:ok, :invite_not_required}
else
Invites.use_invite(invite_token)
end
end)
|> Multi.insert(:add_user, fn %{users_count: count} -> |> Multi.insert(:add_user, fn %{users_count: count} ->
# if no registered users, make first user an admin # if no registered users, make first user an admin
role = if count == 0, do: :admin, else: :user role = if count == 0, do: :admin, else: :user
@ -126,6 +138,7 @@ defmodule Lokal.Accounts do
|> Repo.transaction() |> Repo.transaction()
|> case do |> case do
{:ok, %{add_user: user}} -> {:ok, user} {:ok, %{add_user: user}} -> {:ok, user}
{:error, :use_invite, :invalid_token, _changes_so_far} -> {:error, :invalid_token}
{:error, :add_user, changeset, _changes_so_far} -> {:error, changeset} {:error, :add_user, changeset, _changes_so_far} -> {:error, changeset}
end end
end end
@ -144,8 +157,9 @@ defmodule Lokal.Accounts do
""" """
@spec change_user_registration() :: User.changeset() @spec change_user_registration() :: User.changeset()
@spec change_user_registration(attrs :: map()) :: User.changeset() @spec change_user_registration(attrs :: map()) :: User.changeset()
def change_user_registration(attrs \\ %{}), def change_user_registration(attrs \\ %{}) do
do: User.registration_changeset(attrs, hash_password: false) User.registration_changeset(attrs, hash_password: false)
end
## Settings ## Settings
@ -160,7 +174,9 @@ defmodule Lokal.Accounts do
""" """
@spec change_user_email(User.t()) :: User.changeset() @spec change_user_email(User.t()) :: User.changeset()
@spec change_user_email(User.t(), attrs :: map()) :: User.changeset() @spec change_user_email(User.t(), attrs :: map()) :: User.changeset()
def change_user_email(user, attrs \\ %{}), do: User.email_changeset(user, attrs) def change_user_email(user, attrs \\ %{}) do
User.email_changeset(user, attrs)
end
@doc """ @doc """
Returns an `%Changeset{}` for changing the user role. Returns an `%Changeset{}` for changing the user role.
@ -172,7 +188,9 @@ defmodule Lokal.Accounts do
""" """
@spec change_user_role(User.t(), User.role()) :: User.changeset() @spec change_user_role(User.t(), User.role()) :: User.changeset()
def change_user_role(user, role), do: User.role_changeset(user, role) def change_user_role(user, role) do
User.role_changeset(user, role)
end
@doc """ @doc """
Emulates that the email will change without actually changing Emulates that the email will change without actually changing
@ -262,8 +280,9 @@ defmodule Lokal.Accounts do
""" """
@spec change_user_password(User.t(), attrs :: map()) :: User.changeset() @spec change_user_password(User.t(), attrs :: map()) :: User.changeset()
def change_user_password(user, attrs \\ %{}), def change_user_password(user, attrs \\ %{}) do
do: User.password_changeset(user, attrs, hash_password: false) User.password_changeset(user, attrs, hash_password: false)
end
@doc """ @doc """
Updates the user password. Updates the user password.
@ -314,7 +333,9 @@ defmodule Lokal.Accounts do
""" """
@spec change_user_locale(User.t()) :: User.changeset() @spec change_user_locale(User.t()) :: User.changeset()
def change_user_locale(%{locale: locale} = user), do: User.locale_changeset(user, locale) def change_user_locale(%{locale: locale} = user) do
User.locale_changeset(user, locale)
end
@doc """ @doc """
Updates the user locale. Updates the user locale.
@ -328,8 +349,9 @@ defmodule Lokal.Accounts do
""" """
@spec update_user_locale(User.t(), locale :: String.t()) :: @spec update_user_locale(User.t(), locale :: String.t()) ::
{:ok, User.t()} | {:error, User.changeset()} {:ok, User.t()} | {:error, User.changeset()}
def update_user_locale(user, locale), def update_user_locale(user, locale) do
do: user |> User.locale_changeset(locale) |> Repo.update() user |> User.locale_changeset(locale) |> Repo.update()
end
@doc """ @doc """
Deletes a user. must be performed by an admin or the same user! Deletes a user. must be performed by an admin or the same user!
@ -346,8 +368,13 @@ defmodule Lokal.Accounts do
""" """
@spec delete_user!(user_to_delete :: User.t(), User.t()) :: User.t() @spec delete_user!(user_to_delete :: User.t(), User.t()) :: User.t()
def delete_user!(user, %User{role: :admin}), do: user |> Repo.delete!() def delete_user!(user, %User{role: :admin}) do
def delete_user!(%User{id: user_id} = user, %User{id: user_id}), do: user |> Repo.delete!() user |> Repo.delete!()
end
def delete_user!(%User{id: user_id} = user, %User{id: user_id}) do
user |> Repo.delete!()
end
## Session ## Session
@ -375,7 +402,7 @@ defmodule Lokal.Accounts do
""" """
@spec delete_session_token(token :: String.t()) :: :ok @spec delete_session_token(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")) UserToken.token_and_context_query(token, "session") |> Repo.delete_all()
:ok :ok
end end

View File

@ -1,4 +1,4 @@
defmodule Lokal.Invites.Invite do defmodule Lokal.Accounts.Invite do
@moduledoc """ @moduledoc """
An invite, created by an admin to allow someone to join their instance. An 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 invite can be enabled or disabled, and can have an optional number of uses if
@ -8,7 +8,7 @@ defmodule Lokal.Invites.Invite do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Ecto.{Changeset, UUID} alias Ecto.{Changeset, UUID}
alias Lokal.{Accounts.User, Invites.Invite} alias Lokal.Accounts.User
@primary_key {:id, :binary_id, autogenerate: true} @primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id @foreign_key_type :binary_id
@ -23,10 +23,10 @@ defmodule Lokal.Invites.Invite do
timestamps() timestamps()
end end
@type t :: %Invite{ @type t :: %__MODULE__{
id: id(), id: id(),
name: String.t(), name: String.t(),
token: String.t(), token: token(),
uses_left: integer() | nil, uses_left: integer() | nil,
disabled_at: NaiveDateTime.t(), disabled_at: NaiveDateTime.t(),
user: User.t(), user: User.t(),
@ -34,24 +34,27 @@ defmodule Lokal.Invites.Invite do
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_invite :: %Invite{} @type new_invite :: %__MODULE__{}
@type id :: UUID.t() @type id :: UUID.t()
@type changeset :: Changeset.t(t() | new_invite())
@type token :: String.t()
@doc false @doc false
@spec create_changeset(new_invite(), attrs :: map()) :: Changeset.t(new_invite()) @spec create_changeset(User.t(), token(), attrs :: map()) :: changeset()
def create_changeset(invite, attrs) do def create_changeset(%User{id: user_id}, token, attrs) do
invite %__MODULE__{}
|> cast(attrs, [:name, :token, :uses_left, :disabled_at, :user_id]) |> change(token: token, user_id: user_id)
|> validate_required([:name, :token, :user_id])
|> validate_number(:uses_left, greater_than_or_equal_to: 0)
end
@doc false
@spec update_changeset(t() | new_invite(), attrs :: map()) :: Changeset.t(t() | new_invite())
def update_changeset(invite, attrs) do
invite
|> cast(attrs, [:name, :uses_left, :disabled_at]) |> cast(attrs, [:name, :uses_left, :disabled_at])
|> validate_required([:name, :token, :user_id]) |> validate_required([:name, :token, :user_id])
|> validate_number(:uses_left, greater_than_or_equal_to: 0) |> validate_number(:uses_left, greater_than_or_equal_to: 0)
end end
@doc false
@spec update_changeset(t() | new_invite(), attrs :: map()) :: changeset()
def update_changeset(invite, attrs) do
invite
|> cast(attrs, [:name, :uses_left, :disabled_at])
|> validate_required([:name])
|> validate_number(:uses_left, greater_than_or_equal_to: 0)
end
end end

View File

@ -0,0 +1,187 @@
defmodule Lokal.Accounts.Invites do
@moduledoc """
The Invites context.
"""
import Ecto.Query, warn: false
alias Ecto.Multi
alias Lokal.Accounts.{Invite, User}
alias Lokal.Repo
@invite_token_length 20
@doc """
Returns the list of invites.
## Examples
iex> list_invites(%User{id: 123, role: :admin})
[%Invite{}, ...]
"""
@spec list_invites(User.t()) :: [Invite.t()]
def list_invites(%User{role: :admin}) do
Repo.all(from i in Invite, order_by: i.name)
end
@doc """
Gets a single invite for a user
Raises `Ecto.NoResultsError` if the Invite does not exist.
## Examples
iex> get_invite!(123, %User{id: 123, role: :admin})
%Invite{}
> get_invite!(456, %User{id: 123, role: :admin})
** (Ecto.NoResultsError)
"""
@spec get_invite!(Invite.id(), User.t()) :: Invite.t()
def get_invite!(id, %User{role: :admin}) do
Repo.get!(Invite, id)
end
@doc """
Returns if an invite token is still valid
## Examples
iex> valid_invite_token?("valid_token")
%Invite{}
iex> valid_invite_token?("invalid_token")
nil
"""
@spec valid_invite_token?(Invite.token() | nil) :: boolean()
def valid_invite_token?(token) when token in [nil, ""], do: false
def valid_invite_token?(token) do
Repo.exists?(
from i in Invite,
where: i.token == ^token,
where: i.disabled_at |> is_nil()
)
end
@doc """
Uses invite by decrementing uses_left, or marks invite invalid if it's been
completely used.
"""
@spec use_invite(Invite.token()) :: {:ok, Invite.t()} | {:error, :invalid_token}
def use_invite(invite_token) do
Multi.new()
|> Multi.run(:invite, fn _changes_so_far, _repo ->
invite_token |> get_invite_by_token()
end)
|> Multi.update(:decrement_invite, fn %{invite: invite} ->
decrement_invite_changeset(invite)
end)
|> Repo.transaction()
|> case do
{:ok, %{decrement_invite: invite}} -> {:ok, invite}
{:error, :invite, :invalid_token, _changes_so_far} -> {:error, :invalid_token}
end
end
@spec get_invite_by_token(Invite.token()) :: {:ok, Invite.t()} | {:error, :invalid_token}
defp get_invite_by_token(token) do
Repo.one(
from i in Invite,
where: i.token == ^token,
where: i.disabled_at |> is_nil()
)
|> case do
nil -> {:error, :invalid_token}
invite -> {:ok, invite}
end
end
@spec decrement_invite_changeset(Invite.t()) :: Invite.changeset()
defp decrement_invite_changeset(%Invite{uses_left: nil} = invite) do
invite |> Invite.update_changeset(%{})
end
defp decrement_invite_changeset(%Invite{uses_left: 1} = invite) do
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
invite |> Invite.update_changeset(%{uses_left: 0, disabled_at: now})
end
defp decrement_invite_changeset(%Invite{uses_left: uses_left} = invite) do
invite |> Invite.update_changeset(%{uses_left: uses_left - 1})
end
@doc """
Creates a invite.
## Examples
iex> create_invite(%User{id: 123, role: :admin}, %{field: value})
{:ok, %Invite{}}
iex> create_invite(%User{id: 123, role: :admin}, %{field: bad_value})
{:error, %Changeset{}}
"""
@spec create_invite(User.t(), attrs :: map()) ::
{:ok, Invite.t()} | {:error, Invite.changeset()}
def create_invite(%User{role: :admin} = user, attrs) do
token =
:crypto.strong_rand_bytes(@invite_token_length)
|> Base.url_encode64()
|> binary_part(0, @invite_token_length)
Invite.create_changeset(user, token, attrs) |> Repo.insert()
end
@doc """
Updates a invite.
## Examples
iex> update_invite(invite, %{field: new_value}, %User{id: 123, role: :admin})
{:ok, %Invite{}}
iex> update_invite(invite, %{field: bad_value}, %User{id: 123, role: :admin})
{:error, %Changeset{}}
"""
@spec update_invite(Invite.t(), attrs :: map(), User.t()) ::
{:ok, Invite.t()} | {:error, Invite.changeset()}
def update_invite(invite, attrs, %User{role: :admin}) do
invite |> Invite.update_changeset(attrs) |> Repo.update()
end
@doc """
Deletes a invite.
## Examples
iex> delete_invite(invite, %User{id: 123, role: :admin})
{:ok, %Invite{}}
iex> delete_invite(invite, %User{id: 123, role: :admin})
{:error, %Changeset{}}
"""
@spec delete_invite(Invite.t(), User.t()) ::
{:ok, Invite.t()} | {:error, Invite.changeset()}
def delete_invite(invite, %User{role: :admin}) do
invite |> Repo.delete()
end
@doc """
Deletes a invite.
## Examples
iex> delete_invite(invite, %User{id: 123, role: :admin})
%Invite{}
"""
@spec delete_invite!(Invite.t(), User.t()) :: Invite.t()
def delete_invite!(invite, %User{role: :admin}) do
invite |> Repo.delete!()
end
end

View File

@ -7,7 +7,7 @@ defmodule Lokal.Accounts.User do
import Ecto.Changeset import Ecto.Changeset
import LokalWeb.Gettext import LokalWeb.Gettext
alias Ecto.{Changeset, UUID} alias Ecto.{Changeset, UUID}
alias Lokal.{Accounts.User, Invites.Invite} alias Lokal.Accounts.{Invite, User}
@derive {Jason.Encoder, @derive {Jason.Encoder,
only: [ only: [

View File

@ -5,6 +5,8 @@ defmodule Lokal.Accounts.UserToken do
use Ecto.Schema use Ecto.Schema
import Ecto.Query import Ecto.Query
alias Ecto.{Association, UUID}
alias Lokal.Accounts.User
@hash_algorithm :sha256 @hash_algorithm :sha256
@rand_size 32 @rand_size 32
@ -22,11 +24,25 @@ defmodule Lokal.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, Lokal.Accounts.User
belongs_to :user, User
timestamps(updated_at: false) timestamps(updated_at: false)
end end
@type t :: %__MODULE__{
id: id(),
token: token(),
context: String.t(),
sent_to: String.t(),
user: User.t() | Association.NotLoaded.t(),
user_id: User.id() | nil,
inserted_at: NaiveDateTime.t()
}
@type new_user_token :: %__MODULE__{}
@type id :: UUID.t()
@type token :: binary()
@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
@ -34,7 +50,7 @@ defmodule Lokal.Accounts.UserToken do
""" """
def build_session_token(user) do def build_session_token(user) do
token = :crypto.strong_rand_bytes(@rand_size) token = :crypto.strong_rand_bytes(@rand_size)
{token, %Lokal.Accounts.UserToken{token: token, context: "session", user_id: user.id}} {token, %__MODULE__{token: token, context: "session", user_id: user.id}}
end end
@doc """ @doc """
@ -69,7 +85,7 @@ defmodule Lokal.Accounts.UserToken do
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),
%Lokal.Accounts.UserToken{ %__MODULE__{
token: hashed_token, token: hashed_token,
context: context, context: context,
sent_to: sent_to, sent_to: sent_to,
@ -129,17 +145,17 @@ defmodule Lokal.Accounts.UserToken do
Returns the given token with the given context. Returns the given token with the given context.
""" """
def token_and_context_query(token, context) do def token_and_context_query(token, context) do
from Lokal.Accounts.UserToken, where: [token: ^token, context: ^context] from __MODULE__, 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(user, :all) do
from t in Lokal.Accounts.UserToken, where: t.user_id == ^user.id from t in __MODULE__, where: t.user_id == ^user.id
end end
def user_and_contexts_query(user, [_ | _] = contexts) do def user_and_contexts_query(user, [_ | _] = contexts) do
from t in Lokal.Accounts.UserToken, where: t.user_id == ^user.id and t.context in ^contexts from t in __MODULE__, where: t.user_id == ^user.id and t.context in ^contexts
end end
end end

View File

@ -1,173 +0,0 @@
defmodule Lokal.Invites do
@moduledoc """
The Invites context.
"""
import Ecto.Query, warn: false
alias Ecto.Changeset
alias Lokal.{Accounts.User, Invites.Invite, Repo}
@invite_token_length 20
@doc """
Returns the list of invites.
## Examples
iex> list_invites(%User{id: 123, role: :admin})
[%Invite{}, ...]
"""
@spec list_invites(User.t()) :: [Invite.t()]
def list_invites(%User{role: :admin}) do
Repo.all(from i in Invite, order_by: i.name)
end
@doc """
Gets a single invite.
Raises `Ecto.NoResultsError` if the Invite does not exist.
## Examples
iex> get_invite!(123, %User{id: 123, role: :admin})
%Invite{}
iex> get_invite!(456, %User{id: 123, role: :admin})
** (Ecto.NoResultsError)
"""
@spec get_invite!(Invite.id(), User.t()) :: Invite.t()
def get_invite!(id, %User{role: :admin}) do
Repo.get!(Invite, id)
end
@doc """
Returns a valid invite or nil based on the attempted token
## Examples
iex> get_invite_by_token("valid_token")
%Invite{}
iex> get_invite_by_token("invalid_token")
nil
"""
@spec get_invite_by_token(token :: String.t() | nil) :: Invite.t() | nil
def get_invite_by_token(nil), do: nil
def get_invite_by_token(""), do: nil
def get_invite_by_token(token) do
Repo.one(
from(i in Invite,
where: i.token == ^token and i.disabled_at |> is_nil()
)
)
end
@doc """
Uses invite by decrementing uses_left, or marks invite invalid if it's been
completely used.
"""
@spec use_invite!(Invite.t()) :: Invite.t()
def use_invite!(%Invite{uses_left: nil} = invite), do: invite
def use_invite!(%Invite{uses_left: uses_left} = invite) do
new_uses_left = uses_left - 1
attrs =
if new_uses_left <= 0 do
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
%{"uses_left" => 0, "disabled_at" => now}
else
%{"uses_left" => new_uses_left}
end
invite |> Invite.update_changeset(attrs) |> Repo.update!()
end
@doc """
Creates a invite.
## Examples
iex> create_invite(%User{id: 123, role: :admin}, %{field: value})
{:ok, %Invite{}}
iex> create_invite(%User{id: 123, role: :admin}, %{field: bad_value})
{:error, %Changeset{}}
"""
@spec create_invite(User.t(), attrs :: map()) ::
{:ok, Invite.t()} | {:error, Changeset.t(Invite.new_invite())}
def create_invite(%User{id: user_id, role: :admin}, attrs) do
token =
:crypto.strong_rand_bytes(@invite_token_length)
|> Base.url_encode64()
|> binary_part(0, @invite_token_length)
attrs = attrs |> Map.merge(%{"user_id" => user_id, "token" => token})
%Invite{} |> Invite.create_changeset(attrs) |> Repo.insert()
end
@doc """
Updates a invite.
## Examples
iex> update_invite(invite, %{field: new_value}, %User{id: 123, role: :admin})
{:ok, %Invite{}}
iex> update_invite(invite, %{field: bad_value}, %User{id: 123, role: :admin})
{:error, %Changeset{}}
"""
@spec update_invite(Invite.t(), attrs :: map(), User.t()) ::
{:ok, Invite.t()} | {:error, Changeset.t(Invite.t())}
def update_invite(invite, attrs, %User{role: :admin}),
do: invite |> Invite.update_changeset(attrs) |> Repo.update()
@doc """
Deletes a invite.
## Examples
iex> delete_invite(invite, %User{id: 123, role: :admin})
{:ok, %Invite{}}
iex> delete_invite(invite, %User{id: 123, role: :admin})
{:error, %Changeset{}}
"""
@spec delete_invite(Invite.t(), User.t()) ::
{:ok, Invite.t()} | {:error, Changeset.t(Invite.t())}
def delete_invite(invite, %User{role: :admin}), do: invite |> Repo.delete()
@doc """
Deletes a invite.
## Examples
iex> delete_invite(invite, %User{id: 123, role: :admin})
%Invite{}
"""
@spec delete_invite!(Invite.t(), User.t()) :: Invite.t()
def delete_invite!(invite, %User{role: :admin}), do: invite |> Repo.delete!()
@doc """
Returns an `%Changeset{}` for tracking invite changes.
## Examples
iex> change_invite(invite)
%Changeset{data: %Invite{}}
"""
@spec change_invite(Invite.t() | Invite.new_invite()) ::
Changeset.t(Invite.t() | Invite.new_invite())
@spec change_invite(Invite.t() | Invite.new_invite(), attrs :: map()) ::
Changeset.t(Invite.t() | Invite.new_invite())
def change_invite(invite, attrs \\ %{}), do: invite |> Invite.update_changeset(attrs)
end

View File

@ -1,14 +1,12 @@
defmodule LokalWeb.UserRegistrationController do defmodule LokalWeb.UserRegistrationController do
use LokalWeb, :controller use LokalWeb, :controller
import LokalWeb.Gettext import LokalWeb.Gettext
alias Lokal.{Accounts, Invites} alias Lokal.{Accounts, Accounts.Invites}
alias LokalWeb.{Endpoint, HomeLive} alias LokalWeb.{Endpoint, HomeLive}
def new(conn, %{"invite" => invite_token}) do def new(conn, %{"invite" => invite_token}) do
invite = Invites.get_invite_by_token(invite_token) if Invites.valid_invite_token?(invite_token) do
conn |> render_new(invite_token)
if invite do
conn |> render_new(invite)
else else
conn conn
|> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired")) |> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired"))
@ -27,19 +25,17 @@ defmodule LokalWeb.UserRegistrationController do
end end
# renders new user registration page # renders new user registration page
defp render_new(conn, invite \\ nil) do defp render_new(conn, invite_token \\ nil) do
render(conn, "new.html", render(conn, "new.html",
changeset: Accounts.change_user_registration(), changeset: Accounts.change_user_registration(),
invite: invite, invite_token: invite_token,
page_title: gettext("Register") page_title: gettext("Register")
) )
end end
def create(conn, %{"user" => %{"invite_token" => invite_token}} = attrs) do def create(conn, %{"user" => %{"invite_token" => invite_token}} = attrs) do
invite = Invites.get_invite_by_token(invite_token) if Invites.valid_invite_token?(invite_token) do
conn |> create_user(attrs, invite_token)
if invite do
conn |> create_user(attrs, invite)
else else
conn conn
|> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired")) |> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired"))
@ -57,13 +53,9 @@ defmodule LokalWeb.UserRegistrationController do
end end
end end
defp create_user(conn, %{"user" => user_params}, invite \\ nil) do defp create_user(conn, %{"user" => user_params}, invite_token \\ nil) do
case Accounts.register_user(user_params) do case Accounts.register_user(user_params, invite_token) do
{:ok, user} -> {:ok, user} ->
unless invite |> is_nil() do
invite |> Invites.use_invite!()
end
Accounts.deliver_user_confirmation_instructions( Accounts.deliver_user_confirmation_instructions(
user, user,
&Routes.user_confirmation_url(conn, :confirm, &1) &Routes.user_confirmation_url(conn, :confirm, &1)
@ -73,8 +65,13 @@ defmodule LokalWeb.UserRegistrationController do
|> put_flash(:info, dgettext("prompts", "Please check your email to verify your account")) |> put_flash(:info, dgettext("prompts", "Please check your email to verify your account"))
|> redirect(to: Routes.user_session_path(Endpoint, :new)) |> redirect(to: Routes.user_session_path(Endpoint, :new))
{:error, :invalid_token} ->
conn
|> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive))
{:error, %Ecto.Changeset{} = changeset} -> {:error, %Ecto.Changeset{} = changeset} ->
conn |> render("new.html", changeset: changeset, invite: invite) conn |> render("new.html", changeset: changeset, invite_token: invite_token)
end end
end end
end end

View File

@ -1,11 +1,11 @@
defmodule LokalWeb.InviteLive.FormComponent do defmodule LokalWeb.InviteLive.FormComponent do
@moduledoc """ @moduledoc """
Livecomponent that can update or create an Lokal.Invites.Invite Livecomponent that can update or create an Lokal.Accounts.Invite
""" """
use LokalWeb, :live_component use LokalWeb, :live_component
alias Ecto.Changeset alias Ecto.Changeset
alias Lokal.{Accounts.User, Invites, Invites.Invite} alias Lokal.Accounts.{Invite, Invites, User}
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@impl true @impl true
@ -13,23 +13,44 @@ defmodule LokalWeb.InviteLive.FormComponent do
%{:invite => Invite.t(), :current_user => User.t(), optional(any) => any}, %{:invite => Invite.t(), :current_user => User.t(), optional(any) => any},
Socket.t() Socket.t()
) :: {:ok, Socket.t()} ) :: {:ok, Socket.t()}
def update(%{invite: invite} = assigns, socket) do def update(%{invite: _invite} = assigns, socket) do
{:ok, socket |> assign(assigns) |> assign(:changeset, Invites.change_invite(invite))} {:ok, socket |> assign(assigns) |> assign_changeset(%{})}
end end
@impl true @impl true
def handle_event( def handle_event("validate", %{"invite" => invite_params}, socket) do
"validate", {:noreply, socket |> assign_changeset(invite_params)}
%{"invite" => invite_params},
%{assigns: %{invite: invite}} = socket
) do
{:noreply, socket |> assign(:changeset, invite |> Invites.change_invite(invite_params))}
end end
def handle_event("save", %{"invite" => invite_params}, %{assigns: %{action: action}} = socket) do def handle_event("save", %{"invite" => invite_params}, %{assigns: %{action: action}} = socket) do
save_invite(socket, action, invite_params) save_invite(socket, action, invite_params)
end end
defp assign_changeset(
%{assigns: %{action: action, current_user: user, invite: invite}} = socket,
invite_params
) do
changeset_action =
case action do
:new -> :insert
:edit -> :update
end
changeset =
case action do
:new -> Invite.create_changeset(user, "example_token", invite_params)
:edit -> invite |> Invite.update_changeset(invite_params)
end
changeset =
case changeset |> Changeset.apply_action(changeset_action) do
{:ok, _data} -> changeset
{:error, changeset} -> changeset
end
socket |> assign(:changeset, changeset)
end
defp save_invite( defp save_invite(
%{assigns: %{current_user: current_user, invite: invite, return_to: return_to}} = socket, %{assigns: %{current_user: current_user, invite: invite, return_to: return_to}} = socket,
:edit, :edit,
@ -38,10 +59,8 @@ defmodule LokalWeb.InviteLive.FormComponent do
socket = socket =
case invite |> Invites.update_invite(invite_params, current_user) do case invite |> Invites.update_invite(invite_params, current_user) do
{:ok, %{name: invite_name}} -> {:ok, %{name: invite_name}} ->
prompt = prompt = dgettext("prompts", "%{name} updated successfully", name: invite_name)
dgettext("prompts", "%{invite_name} updated successfully", invite_name: invite_name) socket |> put_flash(:info, prompt) |> push_navigate(to: return_to)
socket |> put_flash(:info, prompt) |> push_redirect(to: return_to)
{:error, %Changeset{} = changeset} -> {:error, %Changeset{} = changeset} ->
socket |> assign(:changeset, changeset) socket |> assign(:changeset, changeset)
@ -58,10 +77,8 @@ defmodule LokalWeb.InviteLive.FormComponent do
socket = socket =
case current_user |> Invites.create_invite(invite_params) do case current_user |> Invites.create_invite(invite_params) do
{:ok, %{name: invite_name}} -> {:ok, %{name: invite_name}} ->
prompt = prompt = dgettext("prompts", "%{name} created successfully", name: invite_name)
dgettext("prompts", "%{invite_name} created successfully", invite_name: invite_name) socket |> put_flash(:info, prompt) |> push_navigate(to: return_to)
socket |> put_flash(:info, prompt) |> push_redirect(to: return_to)
{:error, %Changeset{} = changeset} -> {:error, %Changeset{} = changeset} ->
socket |> assign(changeset: changeset) socket |> assign(changeset: changeset)

View File

@ -1,11 +1,12 @@
defmodule LokalWeb.InviteLive.Index do defmodule LokalWeb.InviteLive.Index do
@moduledoc """ @moduledoc """
Liveview to show a Lokal.Invites.Invite index Liveview to show a Lokal.Accounts.Invite index
""" """
use LokalWeb, :live_view use LokalWeb, :live_view
import LokalWeb.Components.{InviteCard, UserCard} import LokalWeb.Components.{InviteCard, UserCard}
alias Lokal.{Accounts, Invites, Invites.Invite} alias Lokal.Accounts
alias Lokal.Accounts.{Invite, Invites}
alias LokalWeb.{Endpoint, HomeLive} alias LokalWeb.{Endpoint, HomeLive}
alias Phoenix.LiveView.JS alias Phoenix.LiveView.JS

View File

@ -13,8 +13,8 @@
<%= dgettext("errors", "Oops, something went wrong! Please check the errors below.") %> <%= dgettext("errors", "Oops, something went wrong! Please check the errors below.") %>
</p> </p>
<%= if @invite do %> <%= if @invite_token do %>
<%= hidden_input(f, :invite_token, value: @invite.token) %> <%= hidden_input(f, :invite_token, value: @invite_token) %>
<% end %> <% end %>
<%= label(f, :email, class: "title text-lg text-primary-600") %> <%= label(f, :email, class: "title text-lg text-primary-600") %>

View File

@ -37,7 +37,7 @@ msgid "Invite Disabled"
msgstr "" msgstr ""
#: lib/lokal_web/components/topbar.ex:61 #: lib/lokal_web/components/topbar.ex:61
#: lib/lokal_web/live/invite_live/index.ex:41 #: lib/lokal_web/live/invite_live/index.ex:42
#: lib/lokal_web/live/invite_live/index.html.heex:3 #: lib/lokal_web/live/invite_live/index.html.heex:3
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invites" msgid "Invites"
@ -63,7 +63,7 @@ msgstr ""
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "" msgstr ""
#: lib/lokal_web/controllers/user_registration_controller.ex:34 #: lib/lokal_web/controllers/user_registration_controller.ex:32
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Register" msgid "Register"
msgstr "" msgstr ""
@ -94,7 +94,7 @@ msgstr ""
msgid "Disable" msgid "Disable"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:33 #: lib/lokal_web/live/invite_live/index.ex:34
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Edit Invite" msgid "Edit Invite"
msgstr "" msgstr ""
@ -109,7 +109,7 @@ msgstr ""
msgid "Name" msgid "Name"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:37 #: lib/lokal_web/live/invite_live/index.ex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "New Invite" msgid "New Invite"
msgstr "" msgstr ""

View File

@ -37,7 +37,7 @@ msgid "Invite Disabled"
msgstr "" msgstr ""
#: lib/lokal_web/components/topbar.ex:61 #: lib/lokal_web/components/topbar.ex:61
#: lib/lokal_web/live/invite_live/index.ex:41 #: lib/lokal_web/live/invite_live/index.ex:42
#: lib/lokal_web/live/invite_live/index.html.heex:3 #: lib/lokal_web/live/invite_live/index.html.heex:3
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invites" msgid "Invites"
@ -63,7 +63,7 @@ msgstr ""
msgid "Reconnecting..." msgid "Reconnecting..."
msgstr "" msgstr ""
#: lib/lokal_web/controllers/user_registration_controller.ex:34 #: lib/lokal_web/controllers/user_registration_controller.ex:32
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Register" msgid "Register"
msgstr "" msgstr ""
@ -94,7 +94,7 @@ msgstr ""
msgid "Disable" msgid "Disable"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:33 #: lib/lokal_web/live/invite_live/index.ex:34
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Edit Invite" msgid "Edit Invite"
msgstr "" msgstr ""
@ -109,7 +109,7 @@ msgstr ""
msgid "Name" msgid "Name"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:37 #: lib/lokal_web/live/invite_live/index.ex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "New Invite" msgid "New Invite"
msgstr "" msgstr ""

View File

@ -140,14 +140,15 @@ msgstr ""
msgid "Reset password link is invalid or it has expired." msgid "Reset password link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/lokal_web/controllers/user_registration_controller.ex:24 #: lib/lokal_web/controllers/user_registration_controller.ex:22
#: lib/lokal_web/controllers/user_registration_controller.ex:55 #: lib/lokal_web/controllers/user_registration_controller.ex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, public registration is disabled" msgid "Sorry, public registration is disabled"
msgstr "" msgstr ""
#: lib/lokal_web/controllers/user_registration_controller.ex:14 #: lib/lokal_web/controllers/user_registration_controller.ex:12
#: lib/lokal_web/controllers/user_registration_controller.ex:45 #: lib/lokal_web/controllers/user_registration_controller.ex:41
#: lib/lokal_web/controllers/user_registration_controller.ex:70
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, this invite was not found or expired" msgid "Sorry, this invite was not found or expired"
msgstr "" msgstr ""
@ -198,7 +199,7 @@ msgstr ""
msgid "must have the @ sign and no spaces" msgid "must have the @ sign and no spaces"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:18 #: lib/lokal_web/live/invite_live/index.ex:19
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You are not authorized to view this page" msgid "You are not authorized to view this page"
msgstr "" msgstr ""

View File

@ -60,7 +60,7 @@ msgstr ""
msgid "Password updated successfully." msgid "Password updated successfully."
msgstr "" msgstr ""
#: lib/lokal_web/controllers/user_registration_controller.ex:73 #: lib/lokal_web/controllers/user_registration_controller.ex:65
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Please check your email to verify your account" msgid "Please check your email to verify your account"
msgstr "" msgstr ""
@ -76,7 +76,7 @@ msgstr ""
msgid "Are you sure you want to delete %{email}? This action is permanent!" msgid "Are you sure you want to delete %{email}? This action is permanent!"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:127 #: lib/lokal_web/live/invite_live/index.ex:128
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "" msgstr ""
@ -96,37 +96,27 @@ msgstr ""
msgid "Language updated successfully." msgid "Language updated successfully."
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/form_component.ex:62 #: lib/lokal_web/live/invite_live/index.ex:54
#, elixir-autogen, elixir-format
msgid "%{invite_name} created successfully"
msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:53
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{invite_name} deleted succesfully" msgid "%{invite_name} deleted succesfully"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:114 #: lib/lokal_web/live/invite_live/index.ex:115
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{invite_name} disabled succesfully" msgid "%{invite_name} disabled succesfully"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:90 #: lib/lokal_web/live/invite_live/index.ex:91
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{invite_name} enabled succesfully" msgid "%{invite_name} enabled succesfully"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:68 #: lib/lokal_web/live/invite_live/index.ex:69
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{invite_name} updated succesfully" msgid "%{invite_name} updated succesfully"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/form_component.ex:42 #: lib/lokal_web/live/invite_live/index.ex:140
#, elixir-autogen, elixir-format
msgid "%{invite_name} updated successfully"
msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:139
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{user_email} deleted succesfully" msgid "%{user_email} deleted succesfully"
msgstr "" msgstr ""
@ -145,3 +135,13 @@ msgstr ""
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Register to setup Lokal" msgid "Register to setup Lokal"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/form_component.ex:80
#, elixir-autogen, elixir-format, fuzzy
msgid "%{name} created successfully"
msgstr ""
#: lib/lokal_web/live/invite_live/form_component.ex:62
#, elixir-autogen, elixir-format, fuzzy
msgid "%{name} updated successfully"
msgstr ""

View File

@ -137,14 +137,15 @@ msgstr ""
msgid "Reset password link is invalid or it has expired." msgid "Reset password link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/lokal_web/controllers/user_registration_controller.ex:24 #: lib/lokal_web/controllers/user_registration_controller.ex:22
#: lib/lokal_web/controllers/user_registration_controller.ex:55 #: lib/lokal_web/controllers/user_registration_controller.ex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, public registration is disabled" msgid "Sorry, public registration is disabled"
msgstr "" msgstr ""
#: lib/lokal_web/controllers/user_registration_controller.ex:14 #: lib/lokal_web/controllers/user_registration_controller.ex:12
#: lib/lokal_web/controllers/user_registration_controller.ex:45 #: lib/lokal_web/controllers/user_registration_controller.ex:41
#: lib/lokal_web/controllers/user_registration_controller.ex:70
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Sorry, this invite was not found or expired" msgid "Sorry, this invite was not found or expired"
msgstr "" msgstr ""
@ -195,7 +196,7 @@ msgstr ""
msgid "must have the @ sign and no spaces" msgid "must have the @ sign and no spaces"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:18 #: lib/lokal_web/live/invite_live/index.ex:19
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You are not authorized to view this page" msgid "You are not authorized to view this page"
msgstr "" msgstr ""

View File

@ -60,7 +60,7 @@ msgstr ""
msgid "Password updated successfully." msgid "Password updated successfully."
msgstr "" msgstr ""
#: lib/lokal_web/controllers/user_registration_controller.ex:73 #: lib/lokal_web/controllers/user_registration_controller.ex:65
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Please check your email to verify your account" msgid "Please check your email to verify your account"
msgstr "" msgstr ""
@ -76,7 +76,7 @@ msgstr ""
msgid "Are you sure you want to delete %{email}? This action is permanent!" msgid "Are you sure you want to delete %{email}? This action is permanent!"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:127 #: lib/lokal_web/live/invite_live/index.ex:128
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Copied to clipboard" msgid "Copied to clipboard"
msgstr "" msgstr ""
@ -96,37 +96,27 @@ msgstr ""
msgid "Language updated successfully." msgid "Language updated successfully."
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/form_component.ex:62 #: lib/lokal_web/live/invite_live/index.ex:54
#, elixir-autogen, elixir-format
msgid "%{invite_name} created successfully"
msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:53
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{invite_name} deleted succesfully" msgid "%{invite_name} deleted succesfully"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:114 #: lib/lokal_web/live/invite_live/index.ex:115
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{invite_name} disabled succesfully" msgid "%{invite_name} disabled succesfully"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:90 #: lib/lokal_web/live/invite_live/index.ex:91
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{invite_name} enabled succesfully" msgid "%{invite_name} enabled succesfully"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:68 #: lib/lokal_web/live/invite_live/index.ex:69
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{invite_name} updated succesfully" msgid "%{invite_name} updated succesfully"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/form_component.ex:42 #: lib/lokal_web/live/invite_live/index.ex:140
#, elixir-autogen, elixir-format
msgid "%{invite_name} updated successfully"
msgstr ""
#: lib/lokal_web/live/invite_live/index.ex:139
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{user_email} deleted succesfully" msgid "%{user_email} deleted succesfully"
msgstr "" msgstr ""
@ -145,3 +135,13 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Register to setup Lokal" msgid "Register to setup Lokal"
msgstr "" msgstr ""
#: lib/lokal_web/live/invite_live/form_component.ex:80
#, elixir-autogen, elixir-format
msgid "%{name} created successfully"
msgstr ""
#: lib/lokal_web/live/invite_live/form_component.ex:62
#, elixir-autogen, elixir-format
msgid "%{name} updated successfully"
msgstr ""

View File

@ -0,0 +1,122 @@
defmodule Lokal.InvitesTest do
@moduledoc """
This module tests the Lokal.Accounts.Invites context
"""
use Lokal.DataCase
alias Ecto.Changeset
alias Lokal.Accounts.{Invite, Invites}
@moduletag :invites_test
@valid_attrs %{
"name" => "some name",
"uses_left" => 10
}
@update_attrs %{
"name" => "some updated name",
"uses_left" => 5
}
@invalid_attrs %{
"name" => nil,
"token" => nil
}
describe "invites" do
setup do
current_user = admin_fixture()
{:ok, invite} = Invites.create_invite(current_user, @valid_attrs)
[invite: invite, current_user: current_user]
end
test "list_invites/0 returns all invites", %{invite: invite, current_user: current_user} do
assert Invites.list_invites(current_user) == [invite]
end
test "get_invite!/1 returns the invite with given id",
%{invite: invite, current_user: current_user} do
assert Invites.get_invite!(invite.id, current_user) == invite
end
test "valid_invite_token? returns for valid and invalid invite tokens",
%{invite: %{token: token}} do
refute Invites.valid_invite_token?(nil)
refute Invites.valid_invite_token?("")
assert Invites.valid_invite_token?(token)
end
test "valid_invite_token? does not return true for a disabled invite by token",
%{invite: %{token: token} = invite, current_user: current_user} do
assert Invites.valid_invite_token?(token)
{:ok, _invite} = Invites.update_invite(invite, %{uses_left: 1}, current_user)
{:ok, _invite} = Invites.use_invite(token)
refute Invites.valid_invite_token?(token)
end
test "use_invite/1 successfully uses an unlimited invite",
%{invite: %{token: token} = invite, current_user: current_user} do
{:ok, invite} = Invites.update_invite(invite, %{uses_left: nil}, current_user)
assert {:ok, ^invite} = Invites.use_invite(token)
assert {:ok, ^invite} = Invites.use_invite(token)
assert {:ok, ^invite} = Invites.use_invite(token)
end
test "use_invite/1 successfully decrements an invite", %{invite: %{token: token}} do
assert {:ok, %{uses_left: 9}} = Invites.use_invite(token)
assert {:ok, %{uses_left: 8}} = Invites.use_invite(token)
assert {:ok, %{uses_left: 7}} = Invites.use_invite(token)
end
test "use_invite/1 successfully disactivates an invite",
%{invite: %{token: token} = invite, current_user: current_user} do
{:ok, _invite} = Invites.update_invite(invite, %{uses_left: 1}, current_user)
assert {:ok, %{uses_left: 0, disabled_at: disabled_at}} = Invites.use_invite(token)
assert not is_nil(disabled_at)
end
test "use_invite/1 does not work on disactivated invite",
%{invite: %{token: token} = invite, current_user: current_user} do
{:ok, _invite} = Invites.update_invite(invite, %{uses_left: 1}, current_user)
{:ok, _invite} = Invites.use_invite(token)
assert {:error, :invalid_token} = Invites.use_invite(token)
end
test "create_invite/1 with valid data creates a invite",
%{current_user: current_user} do
assert {:ok, %Invite{} = invite} = Invites.create_invite(current_user, @valid_attrs)
assert invite.name == "some name"
end
test "create_invite/1 with invalid data returns error changeset",
%{current_user: current_user} do
assert {:error, %Changeset{}} = Invites.create_invite(current_user, @invalid_attrs)
end
test "update_invite/2 with valid data updates the invite",
%{invite: invite, current_user: current_user} do
assert {:ok, %Invite{} = new_invite} =
Invites.update_invite(invite, @update_attrs, current_user)
assert new_invite.name == "some updated name"
assert new_invite.uses_left == 5
end
test "update_invite/2 with invalid data returns error changeset",
%{invite: invite, current_user: current_user} do
assert {:error, %Changeset{}} = Invites.update_invite(invite, @invalid_attrs, current_user)
assert invite == Invites.get_invite!(invite.id, current_user)
end
test "delete_invite/1 deletes the invite", %{invite: invite, current_user: current_user} do
assert {:ok, %Invite{}} = Invites.delete_invite(invite, current_user)
assert_raise Ecto.NoResultsError, fn -> Invites.get_invite!(invite.id, current_user) end
end
test "delete_invite!/1 deletes the invite", %{invite: invite, current_user: current_user} do
assert %Invite{} = Invites.delete_invite!(invite, current_user)
assert_raise Ecto.NoResultsError, fn -> Invites.get_invite!(invite.id, current_user) end
end
end
end

View File

@ -1,76 +0,0 @@
defmodule Lokal.InvitesTest do
@moduledoc """
This module tests the Invites context
"""
use Lokal.DataCase
alias Ecto.Changeset
alias Lokal.{Invites, Invites.Invite}
@moduletag :invites_test
@valid_attrs %{
"name" => "some name",
"token" => "some token"
}
@update_attrs %{
"name" => "some updated name",
"token" => "some updated token"
}
@invalid_attrs %{
"name" => nil,
"token" => nil
}
describe "invites" do
setup do
current_user = admin_fixture()
{:ok, invite} = Invites.create_invite(current_user, @valid_attrs)
[invite: invite, current_user: current_user]
end
test "list_invites/0 returns all invites", %{invite: invite, current_user: current_user} do
assert Invites.list_invites(current_user) == [invite]
end
test "get_invite!/1 returns the invite with given id",
%{invite: invite, current_user: current_user} do
assert Invites.get_invite!(invite.id, current_user) == invite
end
test "create_invite/1 with valid data creates a invite",
%{current_user: current_user} do
assert {:ok, %Invite{} = invite} = Invites.create_invite(current_user, @valid_attrs)
assert invite.name == "some name"
end
test "create_invite/1 with invalid data returns error changeset",
%{current_user: current_user} do
assert {:error, %Changeset{}} = Invites.create_invite(current_user, @invalid_attrs)
end
test "update_invite/2 with valid data updates the invite",
%{invite: invite, current_user: current_user} do
assert {:ok, %Invite{} = new_invite} =
Invites.update_invite(invite, @update_attrs, current_user)
assert new_invite.name == "some updated name"
assert new_invite.token == new_invite.token
end
test "update_invite/2 with invalid data returns error changeset",
%{invite: invite, current_user: current_user} do
assert {:error, %Changeset{}} = Invites.update_invite(invite, @invalid_attrs, current_user)
assert invite == Invites.get_invite!(invite.id, current_user)
end
test "delete_invite/1 deletes the invite", %{invite: invite, current_user: current_user} do
assert {:ok, %Invite{}} = Invites.delete_invite(invite, current_user)
assert_raise Ecto.NoResultsError, fn -> Invites.get_invite!(invite.id, current_user) end
end
test "change_invite/1 returns a invite changeset", %{invite: invite} do
assert %Changeset{} = Invites.change_invite(invite)
end
end
end

View File

@ -6,7 +6,7 @@ defmodule LokalWeb.InviteLiveTest do
use LokalWeb.ConnCase use LokalWeb.ConnCase
import Phoenix.LiveViewTest import Phoenix.LiveViewTest
import LokalWeb.Gettext import LokalWeb.Gettext
alias Lokal.Invites alias Lokal.Accounts.Invites
@moduletag :invite_live_test @moduletag :invite_live_test
@create_attrs %{"name" => "some name"} @create_attrs %{"name" => "some name"}