improve invites, record usage

This commit is contained in:
shibao 2023-02-04 17:22:06 -05:00
parent eb75937587
commit cd7220cea3
37 changed files with 902 additions and 614 deletions

View File

@ -1,6 +1,7 @@
# v0.1.8 # v0.1.8
- Fix bug with public registration - Fix bug with public registration
- Improve templates - Improve templates
- Improve invites, record usage
# v0.1.7 # v0.1.7
- Update dependencies - Update dependencies

View File

@ -5,7 +5,7 @@ defmodule Memex.Accounts do
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Memex.{Mailer, Repo} alias Memex.{Mailer, Repo}
alias Memex.Accounts.{User, UserToken} alias Memex.Accounts.{Invite, Invites, User, UserToken}
alias Ecto.{Changeset, Multi} alias Ecto.{Changeset, Multi}
alias Oban.Job alias Oban.Job
@ -25,7 +25,9 @@ defmodule Memex.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 Memex.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,19 +117,27 @@ defmodule Memex.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.insert(:add_user, fn %{users_count: count} -> |> Multi.run(:use_invite, fn _changes_so_far, _repo ->
if allow_registration?() and invite_token |> is_nil() do
{:ok, nil}
else
Invites.use_invite(invite_token)
end
end)
|> Multi.insert(:add_user, fn %{users_count: count, use_invite: invite} ->
# 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
User.registration_changeset(attrs, invite) |> User.role_changeset(role)
User.registration_changeset(attrs) |> User.role_changeset(role)
end) end)
|> 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 +156,9 @@ defmodule Memex.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, nil, hash_password: false)
end
## Settings ## Settings
@ -160,7 +173,9 @@ defmodule Memex.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 +187,9 @@ defmodule Memex.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 +279,9 @@ defmodule Memex.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 +332,9 @@ defmodule Memex.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 +348,9 @@ defmodule Memex.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 +367,13 @@ defmodule Memex.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 +401,7 @@ defmodule Memex.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 Memex.Invites.Invite do defmodule Memex.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
@ -7,8 +7,8 @@ defmodule Memex.Invites.Invite do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Ecto.{Changeset, UUID} alias Ecto.{Association, Changeset, UUID}
alias Memex.{Accounts.User, Invites.Invite} alias Memex.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
@ -18,40 +18,46 @@ defmodule Memex.Invites.Invite do
field :uses_left, :integer, default: nil field :uses_left, :integer, default: nil
field :disabled_at, :naive_datetime field :disabled_at, :naive_datetime
belongs_to :user, User belongs_to :created_by, User
has_many :users, User
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(), created_by: User.t() | nil | Association.NotLoaded.t(),
user_id: User.id(), created_by_id: User.id() | nil,
users: [User.t()] | Association.NotLoaded.t(),
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, created_by_id: user_id)
|> validate_required([:name, :token, :user_id]) |> cast(attrs, [:name, :uses_left, :disabled_at])
|> validate_required([:name, :token, :created_by_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 @doc false
@spec update_changeset(t() | new_invite(), attrs :: map()) :: Changeset.t(t() | new_invite()) @spec update_changeset(t() | new_invite(), attrs :: map()) :: changeset()
def update_changeset(invite, attrs) do def update_changeset(invite, attrs) do
invite invite
|> cast(attrs, [:name, :uses_left, :disabled_at]) |> cast(attrs, [:name, :uses_left, :disabled_at])
|> validate_required([:name, :token, :user_id]) |> validate_required([:name])
|> validate_number(:uses_left, greater_than_or_equal_to: 0) |> validate_number(:uses_left, greater_than_or_equal_to: 0)
end end
end end

View File

@ -0,0 +1,196 @@
defmodule Memex.Accounts.Invites do
@moduledoc """
The Invites context.
"""
import Ecto.Query, warn: false
alias Ecto.Multi
alias Memex.Accounts.{Invite, User}
alias Memex.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 get_use_count(Invite.t(), User.t()) :: non_neg_integer()
def get_use_count(%Invite{id: invite_id}, %User{role: :admin}) do
Repo.one(
from u in User,
where: u.invite_id == ^invite_id,
select: count(u.id)
)
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

@ -6,8 +6,8 @@ defmodule Memex.Accounts.User do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
import MemexWeb.Gettext import MemexWeb.Gettext
alias Ecto.{Changeset, UUID} alias Ecto.{Association, Changeset, UUID}
alias Memex.Invites.Invite alias Memex.Accounts.{Invite, User}
@derive {Jason.Encoder, @derive {Jason.Encoder,
only: [ only: [
@ -30,27 +30,31 @@ defmodule Memex.Accounts.User do
field :role, Ecto.Enum, values: [:admin, :user], default: :user field :role, Ecto.Enum, values: [:admin, :user], default: :user
field :locale, :string field :locale, :string
has_many :invites, Invite, on_delete: :delete_all has_many :created_invites, Invite, foreign_key: :created_by_id
belongs_to :invite, Invite
timestamps() timestamps()
end end
@type t :: %__MODULE__{ @type t :: %User{
id: id(), id: id(),
email: String.t(), email: String.t(),
password: String.t(), password: String.t(),
hashed_password: String.t(), hashed_password: String.t(),
confirmed_at: NaiveDateTime.t(), confirmed_at: NaiveDateTime.t(),
role: role(), role: role(),
invites: [Invite.t()],
locale: String.t() | nil, locale: String.t() | nil,
created_invites: [Invite.t()] | Association.NotLoaded.t(),
invite: Invite.t() | nil | Association.NotLoaded.t(),
invite_id: Invite.id() | nil,
inserted_at: NaiveDateTime.t(), inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() updated_at: NaiveDateTime.t()
} }
@type new_user :: %__MODULE__{} @type new_user :: %User{}
@type id :: UUID.t() @type id :: UUID.t()
@type changeset :: Changeset.t(t() | new_user()) @type changeset :: Changeset.t(t() | new_user())
@type role :: :user | :admin @type role :: :admin | :user
@doc """ @doc """
A user changeset for registration. A user changeset for registration.
@ -69,18 +73,18 @@ defmodule Memex.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(attrs :: map()) :: changeset() @spec registration_changeset(attrs :: map(), Invite.t() | nil) :: changeset()
@spec registration_changeset(attrs :: map(), opts :: keyword()) :: changeset() @spec registration_changeset(attrs :: map(), Invite.t() | nil, opts :: keyword()) :: changeset()
def registration_changeset(attrs, opts \\ []) do def registration_changeset(attrs, invite, opts \\ []) do
%__MODULE__{} %User{}
|> cast(attrs, [:email, :password, :locale]) |> cast(attrs, [:email, :password, :locale])
|> put_change(:invite_id, if(invite, do: invite.id))
|> validate_email() |> validate_email()
|> validate_password(opts) |> validate_password(opts)
end end
@doc """ @doc """
A user changeset for role. A user changeset for role.
""" """
@spec role_changeset(t() | new_user() | changeset(), role()) :: changeset() @spec role_changeset(t() | new_user() | changeset(), role()) :: changeset()
def role_changeset(user, role) do def role_changeset(user, role) do
@ -99,7 +103,8 @@ defmodule Memex.Accounts.User do
|> unique_constraint(:email) |> unique_constraint(:email)
end end
@spec validate_password(changeset(), opts :: keyword()) :: changeset() @spec validate_password(changeset(), opts :: keyword()) ::
changeset()
defp validate_password(changeset, opts) do defp validate_password(changeset, opts) do
changeset changeset
|> validate_required([:password]) |> validate_required([:password])
@ -177,12 +182,12 @@ defmodule Memex.Accounts.User do
`Bcrypt.no_user_verify/0` to avoid timing attacks. `Bcrypt.no_user_verify/0` to avoid timing attacks.
""" """
@spec valid_password?(t(), String.t()) :: boolean() @spec valid_password?(t(), String.t()) :: boolean()
def valid_password?(%__MODULE__{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)
end end
def valid_password?(_, _) do def valid_password?(_invalid_user, _invalid_password) do
Bcrypt.no_user_verify() Bcrypt.no_user_verify()
false false
end end

View File

@ -5,6 +5,8 @@ defmodule Memex.Accounts.UserToken do
use Ecto.Schema use Ecto.Schema
import Ecto.Query import Ecto.Query
alias Ecto.{Association, UUID}
alias Memex.Accounts.User
@hash_algorithm :sha256 @hash_algorithm :sha256
@rand_size 32 @rand_size 32
@ -22,11 +24,25 @@ defmodule Memex.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, Memex.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 Memex.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, %Memex.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 Memex.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),
%Memex.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 Memex.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 Memex.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 Memex.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 Memex.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 Memex.Invites do
@moduledoc """
The Invites context.
"""
import Ecto.Query, warn: false
alias Ecto.Changeset
alias Memex.{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

@ -4,9 +4,19 @@ defmodule MemexWeb.Components.InviteCard do
""" """
use MemexWeb, :component use MemexWeb, :component
alias Memex.Accounts.{Invite, Invites, User}
alias MemexWeb.Endpoint
def invite_card(assigns) do attr :invite, Invite, required: true
assigns = assigns |> assign_new(:code_actions, fn -> [] end) attr :current_user, User, required: true
slot(:inner_block)
slot(:code_actions)
def invite_card(%{invite: invite, current_user: current_user} = assigns) do
assigns =
assigns
|> assign(:use_count, Invites.get_use_count(invite, current_user))
|> assign_new(:code_actions, fn -> [] end)
~H""" ~H"""
<div class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center space-y-4 <div class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center space-y-4
@ -20,8 +30,8 @@ defmodule MemexWeb.Components.InviteCard do
<h2 class="title text-md"> <h2 class="title text-md">
<%= if @invite.uses_left do %> <%= if @invite.uses_left do %>
<%= gettext( <%= gettext(
"uses left: %{uses_left}", "uses left: %{uses_left_count}",
uses_left: @invite.uses_left uses_left_count: @invite.uses_left
) %> ) %>
<% else %> <% else %>
<%= gettext("uses left: unlimited") %> <%= gettext("uses left: unlimited") %>
@ -38,6 +48,10 @@ defmodule MemexWeb.Components.InviteCard do
filename={@invite.name} filename={@invite.name}
/> />
<h2 :if={@use_count != 0} class="title text-md">
<%= gettext("uses: %{uses_count}", uses_count: @use_count) %>
</h2>
<div class="flex flex-row flex-wrap justify-center items-center"> <div class="flex flex-row flex-wrap justify-center items-center">
<code <code
id={"code-#{@invite.id}"} id={"code-#{@invite.id}"}

View File

@ -1,14 +1,12 @@
defmodule MemexWeb.UserRegistrationController do defmodule MemexWeb.UserRegistrationController do
use MemexWeb, :controller use MemexWeb, :controller
import MemexWeb.Gettext import MemexWeb.Gettext
alias Memex.{Accounts, Invites} alias Memex.{Accounts, Accounts.Invites}
alias MemexWeb.HomeLive alias MemexWeb.{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 MemexWeb.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,24 +53,25 @@ defmodule MemexWeb.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)
) )
conn conn
|> 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

@ -7,7 +7,7 @@ defmodule MemexWeb.UserSettingsController do
plug :assign_email_and_password_changesets plug :assign_email_and_password_changesets
def edit(conn, _params) do def edit(conn, _params) do
render(conn, "edit.html", page_title: gettext("Settings")) render(conn, "edit.html", page_title: gettext("settings"))
end end
def update(%{assigns: %{current_user: user}} = conn, %{ def update(%{assigns: %{current_user: user}} = conn, %{
@ -28,7 +28,7 @@ defmodule MemexWeb.UserSettingsController do
:info, :info,
dgettext( dgettext(
"prompts", "prompts",
"A link to confirm your email change has been sent to the new address." "a link to confirm your email change has been sent to the new address."
) )
) )
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: Routes.user_settings_path(conn, :edit))
@ -46,7 +46,7 @@ defmodule MemexWeb.UserSettingsController do
case Accounts.update_user_password(user, password, user_params) do case Accounts.update_user_password(user, password, user_params) do
{:ok, user} -> {:ok, user} ->
conn conn
|> put_flash(:info, dgettext("prompts", "Password updated successfully.")) |> put_flash(:info, dgettext("prompts", "password updated successfully."))
|> put_session(:user_return_to, Routes.user_settings_path(conn, :edit)) |> put_session(:user_return_to, Routes.user_settings_path(conn, :edit))
|> UserAuth.log_in_user(user) |> UserAuth.log_in_user(user)
@ -74,14 +74,14 @@ defmodule MemexWeb.UserSettingsController do
case Accounts.update_user_email(user, token) do case Accounts.update_user_email(user, token) do
:ok -> :ok ->
conn conn
|> put_flash(:info, dgettext("prompts", "Email changed successfully.")) |> put_flash(:info, dgettext("prompts", "email changed successfully."))
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: Routes.user_settings_path(conn, :edit))
:error -> :error ->
conn conn
|> put_flash( |> put_flash(
:error, :error,
dgettext("errors", "Email change link is invalid or it has expired.") dgettext("errors", "email change link is invalid or it has expired.")
) )
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: Routes.user_settings_path(conn, :edit))
end end
@ -92,11 +92,11 @@ defmodule MemexWeb.UserSettingsController do
current_user |> Accounts.delete_user!(current_user) current_user |> Accounts.delete_user!(current_user)
conn conn
|> put_flash(:error, dgettext("prompts", "Your account has been deleted")) |> put_flash(:error, dgettext("prompts", "your account has been deleted"))
|> redirect(to: Routes.live_path(conn, HomeLive)) |> redirect(to: Routes.live_path(conn, HomeLive))
else else
conn conn
|> put_flash(:error, dgettext("errors", "Unable to delete user")) |> put_flash(:error, dgettext("errors", "unable to delete user"))
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: Routes.user_settings_path(conn, :edit))
end end
end end

View File

@ -44,7 +44,7 @@
</div> </div>
<.modal <.modal
if={@live_action == :edit} :if={@live_action == :edit}
return_to={Routes.context_show_path(@socket, :show, @context.slug)} return_to={Routes.context_show_path(@socket, :show, @context.slug)}
> >
<.live_component <.live_component

View File

@ -1,11 +1,11 @@
defmodule MemexWeb.InviteLive.FormComponent do defmodule MemexWeb.InviteLive.FormComponent do
@moduledoc """ @moduledoc """
Livecomponent that can update or create an Memex.Invites.Invite Livecomponent that can update or create an Memex.Accounts.Invite
""" """
use MemexWeb, :live_component use MemexWeb, :live_component
alias Ecto.Changeset alias Ecto.Changeset
alias Memex.{Accounts.User, Invites, Invites.Invite} alias Memex.Accounts.{Invite, Invites, User}
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@impl true @impl true
@ -13,23 +13,44 @@ defmodule MemexWeb.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,9 +59,7 @@ defmodule MemexWeb.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_navigate(to: return_to)
{:error, %Changeset{} = changeset} -> {:error, %Changeset{} = changeset} ->
@ -58,9 +77,7 @@ defmodule MemexWeb.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_navigate(to: return_to)
{:error, %Changeset{} = changeset} -> {:error, %Changeset{} = changeset} ->

View File

@ -1,12 +1,13 @@
defmodule MemexWeb.InviteLive.Index do defmodule MemexWeb.InviteLive.Index do
@moduledoc """ @moduledoc """
Liveview to show a Memex.Invites.Invite index Liveview to show a Memex.Accounts.Invite index
""" """
use MemexWeb, :live_view use MemexWeb, :live_view
import MemexWeb.Components.{InviteCard, UserCard} import MemexWeb.Components.{InviteCard, UserCard}
alias Memex.{Accounts, Invites, Invites.Invite} alias Memex.Accounts
alias MemexWeb.HomeLive alias Memex.Accounts.{Invite, Invites}
alias MemexWeb.{Endpoint, HomeLive}
alias Phoenix.LiveView.JS alias Phoenix.LiveView.JS
@impl true @impl true
@ -15,9 +16,9 @@ defmodule MemexWeb.InviteLive.Index do
if current_user |> Map.get(:role) == :admin do if current_user |> Map.get(:role) == :admin do
socket |> display_invites() socket |> display_invites()
else else
prompt = dgettext("errors", "You are not authorized to view this page") prompt = dgettext("errors", "you are not authorized to view this page")
return_to = Routes.live_path(Endpoint, HomeLive) return_to = Routes.live_path(Endpoint, HomeLive)
socket |> put_flash(:error, prompt) |> push_navigate(to: return_to) socket |> put_flash(:error, prompt) |> push_redirect(to: return_to)
end end
{:ok, socket} {:ok, socket}
@ -61,7 +62,7 @@ defmodule MemexWeb.InviteLive.Index do
) do ) do
socket = socket =
Invites.get_invite!(id, current_user) Invites.get_invite!(id, current_user)
|> Invites.update_invite(%{"uses_left" => nil}, current_user) |> Invites.update_invite(%{uses_left: nil}, current_user)
|> case do |> case do
{:ok, %{name: invite_name}} -> {:ok, %{name: invite_name}} ->
prompt = prompt =
@ -83,7 +84,7 @@ defmodule MemexWeb.InviteLive.Index do
) do ) do
socket = socket =
Invites.get_invite!(id, current_user) Invites.get_invite!(id, current_user)
|> Invites.update_invite(%{"uses_left" => nil, "disabled_at" => nil}, current_user) |> Invites.update_invite(%{uses_left: nil, disabled_at: nil}, current_user)
|> case do |> case do
{:ok, %{name: invite_name}} -> {:ok, %{name: invite_name}} ->
prompt = prompt =
@ -107,7 +108,7 @@ defmodule MemexWeb.InviteLive.Index do
socket = socket =
Invites.get_invite!(id, current_user) Invites.get_invite!(id, current_user)
|> Invites.update_invite(%{"uses_left" => 0, "disabled_at" => now}, current_user) |> Invites.update_invite(%{uses_left: 0, disabled_at: now}, current_user)
|> case do |> case do
{:ok, %{name: invite_name}} -> {:ok, %{name: invite_name}} ->
prompt = prompt =
@ -124,7 +125,7 @@ defmodule MemexWeb.InviteLive.Index do
@impl true @impl true
def handle_event("copy_to_clipboard", _, socket) do def handle_event("copy_to_clipboard", _, socket) do
prompt = dgettext("prompts", "Copied to clipboard") prompt = dgettext("prompts", "copied to clipboard")
{:noreply, socket |> put_flash(:info, prompt)} {:noreply, socket |> put_flash(:info, prompt)}
end end

View File

@ -18,7 +18,7 @@
<% end %> <% end %>
<div class="w-full flex flex-row flex-wrap justify-center items-center"> <div class="w-full flex flex-row flex-wrap justify-center items-center">
<.invite_card :for={invite <- @invites} invite={invite}> <.invite_card :for={invite <- @invites} invite={invite} current_user={@current_user}>
<:code_actions> <:code_actions>
<form phx-submit="copy_to_clipboard"> <form phx-submit="copy_to_clipboard">
<button <button

View File

@ -1,6 +1,6 @@
defmodule MemexWeb.LiveHelpers do defmodule MemexWeb.LiveHelpers do
@moduledoc """ @moduledoc """
Contains resuable methods for all liveviews Contains common helper functions for liveviews
""" """
import Phoenix.Component import Phoenix.Component
@ -31,7 +31,7 @@ defmodule MemexWeb.LiveHelpers do
patch={@return_to} patch={@return_to}
id="modal-bg" id="modal-bg"
class="fade-in fixed z-10 left-0 top-0 class="fade-in fixed z-10 left-0 top-0
w-screen h-screen overflow-hidden w-full h-full overflow-hidden
p-8 flex flex-col justify-center items-center cursor-auto" p-8 flex flex-col justify-center items-center cursor-auto"
style="background-color: rgba(0,0,0,0.4);" style="background-color: rgba(0,0,0,0.4);"
phx-remove={hide_modal()} phx-remove={hide_modal()}
@ -63,7 +63,7 @@ defmodule MemexWeb.LiveHelpers do
<i class="fa-fw fa-lg fas fa-times"></i> <i class="fa-fw fa-lg fas fa-times"></i>
</.link> </.link>
<div class="overflow-x-hidden overflow-y-visible p-8 flex flex-col space-y-4 justify-start items-stretch"> <div class="overflow-x-hidden overflow-y-auto w-full p-8 flex flex-col space-y-4 justify-start items-stretch">
<%= render_slot(@inner_block) %> <%= render_slot(@inner_block) %>
</div> </div>
</div> </div>
@ -71,10 +71,57 @@ defmodule MemexWeb.LiveHelpers do
""" """
end end
def hide_modal(js \\ %JS{}) do defp hide_modal(js \\ %JS{}) do
js js
|> JS.hide(to: "#modal", transition: "fade-out") |> JS.hide(to: "#modal", transition: "fade-out")
|> JS.hide(to: "#modal-bg", transition: "fade-out") |> JS.hide(to: "#modal-bg", transition: "fade-out")
|> JS.hide(to: "#modal-content", transition: "fade-out-scale") |> JS.hide(to: "#modal-content", transition: "fade-out-scale")
end end
@doc """
A toggle button element that can be directed to a liveview or a
live_component's `handle_event/3`.
## Examples
<.toggle_button action="my_liveview_action" value={@some_value}>
<span>Toggle me!</span>
</.toggle_button>
<.toggle_button action="my_live_component_action" target={@myself} value={@some_value}>
<span>Whatever you want</span>
</.toggle_button>
"""
def toggle_button(assigns) do
assigns = assigns |> assign_new(:id, fn -> assigns.action end)
~H"""
<label for={@id} class="inline-flex relative items-center cursor-pointer">
<input
id={@id}
type="checkbox"
value={@value}
checked={@value}
class="sr-only peer"
data-qa={@id}
{
if assigns |> Map.has_key?(:target),
do: %{"phx-click": @action, "phx-value-value": @value, "phx-target": @target},
else: %{"phx-click": @action, "phx-value-value": @value}
}
/>
<div class="w-11 h-6 bg-gray-300 rounded-full peer
peer-focus:ring-4 peer-focus:ring-teal-300 dark:peer-focus:ring-teal-800
peer-checked:bg-gray-600
peer-checked:after:translate-x-full peer-checked:after:border-white
after:content-[''] after:absolute after:top-1 after:left-[2px] after:bg-white after:border-gray-300
after:border after:rounded-full after:h-5 after:w-5
after:transition-all after:duration-250 after:ease-in-out
transition-colors duration-250 ease-in-out">
</div>
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
<%= render_slot(@inner_block) %>
</span>
</label>
"""
end
end end

View File

@ -9,14 +9,12 @@
action={Routes.user_registration_path(@conn, :create)} action={Routes.user_registration_path(@conn, :create)}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >
<div :if={@changeset.action && not @changeset.valid?()} class="alert alert-danger col-span-3"> <p :if={@changeset.action && not @changeset.valid?()} class="alert alert-danger col-span-3">
<p> <%= dgettext("errors", "oops, something went wrong! please check the errors below.") %>
<%= dgettext("errors", "Oops, something went wrong! Please check the errors below.") %>
</p> </p>
</div>
<%= 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-400") %> <%= label(f, :email, class: "title text-lg text-primary-400") %>

View File

@ -89,7 +89,7 @@ msgstr ""
#: lib/memex_web/components/topbar.ex:124 #: lib/memex_web/components/topbar.ex:124
#: lib/memex_web/templates/user_confirmation/new.html.heex:31 #: lib/memex_web/templates/user_confirmation/new.html.heex:31
#: lib/memex_web/templates/user_registration/new.html.heex:46 #: lib/memex_web/templates/user_registration/new.html.heex:44
#: lib/memex_web/templates/user_reset_password/edit.html.heex:47 #: lib/memex_web/templates/user_reset_password/edit.html.heex:47
#: lib/memex_web/templates/user_reset_password/new.html.heex:31 #: lib/memex_web/templates/user_reset_password/new.html.heex:31
#: lib/memex_web/templates/user_session/new.html.heex:3 #: lib/memex_web/templates/user_session/new.html.heex:3
@ -116,7 +116,7 @@ msgstr ""
#: lib/memex_web/components/topbar.ex:115 #: lib/memex_web/components/topbar.ex:115
#: lib/memex_web/templates/user_confirmation/new.html.heex:28 #: lib/memex_web/templates/user_confirmation/new.html.heex:28
#: lib/memex_web/templates/user_registration/new.html.heex:3 #: lib/memex_web/templates/user_registration/new.html.heex:3
#: lib/memex_web/templates/user_registration/new.html.heex:39 #: lib/memex_web/templates/user_registration/new.html.heex:37
#: lib/memex_web/templates/user_reset_password/edit.html.heex:44 #: lib/memex_web/templates/user_reset_password/edit.html.heex:44
#: lib/memex_web/templates/user_reset_password/new.html.heex:28 #: lib/memex_web/templates/user_reset_password/new.html.heex:28
#: lib/memex_web/templates/user_session/new.html.heex:41 #: lib/memex_web/templates/user_session/new.html.heex:41
@ -144,7 +144,7 @@ msgstr ""
msgid "add step" msgid "add step"
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:49 #: lib/memex_web/templates/user_registration/new.html.heex:47
#: lib/memex_web/templates/user_reset_password/new.html.heex:3 #: lib/memex_web/templates/user_reset_password/new.html.heex:3
#: lib/memex_web/templates/user_session/new.html.heex:44 #: lib/memex_web/templates/user_session/new.html.heex:44
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format

View File

@ -89,7 +89,7 @@ msgstr ""
#: lib/memex_web/components/topbar.ex:124 #: lib/memex_web/components/topbar.ex:124
#: lib/memex_web/templates/user_confirmation/new.html.heex:31 #: lib/memex_web/templates/user_confirmation/new.html.heex:31
#: lib/memex_web/templates/user_registration/new.html.heex:46 #: lib/memex_web/templates/user_registration/new.html.heex:44
#: lib/memex_web/templates/user_reset_password/edit.html.heex:47 #: lib/memex_web/templates/user_reset_password/edit.html.heex:47
#: lib/memex_web/templates/user_reset_password/new.html.heex:31 #: lib/memex_web/templates/user_reset_password/new.html.heex:31
#: lib/memex_web/templates/user_session/new.html.heex:3 #: lib/memex_web/templates/user_session/new.html.heex:3
@ -116,7 +116,7 @@ msgstr ""
#: lib/memex_web/components/topbar.ex:115 #: lib/memex_web/components/topbar.ex:115
#: lib/memex_web/templates/user_confirmation/new.html.heex:28 #: lib/memex_web/templates/user_confirmation/new.html.heex:28
#: lib/memex_web/templates/user_registration/new.html.heex:3 #: lib/memex_web/templates/user_registration/new.html.heex:3
#: lib/memex_web/templates/user_registration/new.html.heex:39 #: lib/memex_web/templates/user_registration/new.html.heex:37
#: lib/memex_web/templates/user_reset_password/edit.html.heex:44 #: lib/memex_web/templates/user_reset_password/edit.html.heex:44
#: lib/memex_web/templates/user_reset_password/new.html.heex:28 #: lib/memex_web/templates/user_reset_password/new.html.heex:28
#: lib/memex_web/templates/user_session/new.html.heex:41 #: lib/memex_web/templates/user_session/new.html.heex:41
@ -144,7 +144,7 @@ msgstr ""
msgid "add step" msgid "add step"
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:49 #: lib/memex_web/templates/user_registration/new.html.heex:47
#: lib/memex_web/templates/user_reset_password/new.html.heex:3 #: lib/memex_web/templates/user_reset_password/new.html.heex:3
#: lib/memex_web/templates/user_session/new.html.heex:44 #: lib/memex_web/templates/user_session/new.html.heex:44
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy

View File

@ -51,11 +51,6 @@ msgstr ""
msgid "Reset your password" msgid "Reset your password"
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:10
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:25 #: lib/memex_web/live/invite_live/form_component.html.heex:25
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Uses left" msgid "Uses left"
@ -132,7 +127,7 @@ msgstr ""
msgid "document your processes, attaching contexts to each step" msgid "document your processes, attaching contexts to each step"
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/index.ex:33 #: lib/memex_web/live/invite_live/index.ex:34
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "edit invite" msgid "edit invite"
msgstr "" msgstr ""
@ -152,7 +147,7 @@ msgstr ""
msgid "enable" msgid "enable"
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:34 #: lib/memex_web/templates/user_registration/new.html.heex:32
#: lib/memex_web/templates/user_settings/edit.html.heex:129 #: lib/memex_web/templates/user_settings/edit.html.heex:129
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "english" msgid "english"
@ -178,7 +173,7 @@ msgstr ""
msgid "instance information" msgid "instance information"
msgstr "" msgstr ""
#: lib/memex_web/components/invite_card.ex:32 #: lib/memex_web/components/invite_card.ex:42
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "invite disabled" msgid "invite disabled"
msgstr "" msgstr ""
@ -189,7 +184,7 @@ msgid "invite only"
msgstr "" msgstr ""
#: lib/memex_web/components/topbar.ex:73 #: lib/memex_web/components/topbar.ex:73
#: lib/memex_web/live/invite_live/index.ex:41 #: lib/memex_web/live/invite_live/index.ex:42
#: lib/memex_web/live/invite_live/index.html.heex:3 #: lib/memex_web/live/invite_live/index.html.heex:3
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "invites" msgid "invites"
@ -205,7 +200,7 @@ msgstr ""
msgid "multi-user:" msgid "multi-user:"
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/index.ex:37 #: lib/memex_web/live/invite_live/index.ex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "new invite" msgid "new invite"
msgstr "" msgstr ""
@ -271,7 +266,7 @@ msgstr ""
msgid "public signups" msgid "public signups"
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:34 #: lib/memex_web/controllers/user_registration_controller.ex:32
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "register" msgid "register"
msgstr "" msgstr ""
@ -306,6 +301,7 @@ msgstr ""
msgid "set unlimited" msgid "set unlimited"
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:10
#: lib/memex_web/templates/user_settings/edit.html.heex:3 #: lib/memex_web/templates/user_settings/edit.html.heex:3
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "settings" msgid "settings"
@ -632,7 +628,7 @@ msgstr ""
msgid "while memEx fully supports multiple users, each memEx instance should be treated as a single cohesive and collaborative document." msgid "while memEx fully supports multiple users, each memEx instance should be treated as a single cohesive and collaborative document."
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:30 #: lib/memex_web/templates/user_registration/new.html.heex:28
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "language" msgid "language"
msgstr "" msgstr ""
@ -647,12 +643,7 @@ msgstr ""
msgid "user registered on%{registered_datetime}" msgid "user registered on%{registered_datetime}"
msgstr "" msgstr ""
#: lib/memex_web/components/invite_card.ex:22 #: lib/memex_web/components/invite_card.ex:37
#, elixir-autogen, elixir-format
msgid "uses left: %{uses_left}"
msgstr ""
#: lib/memex_web/components/invite_card.ex:27
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "uses left: unlimited" msgid "uses left: unlimited"
msgstr "" msgstr ""
@ -661,3 +652,13 @@ msgstr ""
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "read more on how to use memEx" msgid "read more on how to use memEx"
msgstr "" msgstr ""
#: lib/memex_web/components/invite_card.ex:32
#, elixir-autogen, elixir-format, fuzzy
msgid "uses left: %{uses_left_count}"
msgstr ""
#: lib/memex_web/components/invite_card.ex:52
#, elixir-autogen, elixir-format
msgid "uses: %{uses_count}"
msgstr ""

View File

@ -10,11 +10,6 @@ msgid ""
msgstr "" msgstr ""
"Language: de\n" "Language: de\n"
#: lib/memex_web/controllers/user_settings_controller.ex:84
#, elixir-autogen, elixir-format
msgid "Email change link is invalid or it has expired."
msgstr ""
#: lib/memex_web/templates/error/error.html.heex:8 #: lib/memex_web/templates/error/error.html.heex:8
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Error" msgid "Error"
@ -25,7 +20,6 @@ msgstr ""
msgid "Invalid email or password" msgid "Invalid email or password"
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:14
#: lib/memex_web/templates/user_reset_password/edit.html.heex:14 #: lib/memex_web/templates/user_reset_password/edit.html.heex:14
#: lib/memex_web/templates/user_settings/edit.html.heex:67 #: lib/memex_web/templates/user_settings/edit.html.heex:67
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -37,33 +31,23 @@ msgstr ""
msgid "Reset password link is invalid or it has expired." msgid "Reset password link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:24 #: lib/memex_web/controllers/user_registration_controller.ex:22
#: lib/memex_web/controllers/user_registration_controller.ex:55 #: lib/memex_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/memex_web/controllers/user_registration_controller.ex:14 #: lib/memex_web/controllers/user_registration_controller.ex:12
#: lib/memex_web/controllers/user_registration_controller.ex:45 #: lib/memex_web/controllers/user_registration_controller.ex:41
#, 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 ""
#: lib/memex_web/controllers/user_settings_controller.ex:99
#, elixir-autogen, elixir-format
msgid "Unable to delete user"
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:54 #: lib/memex_web/controllers/user_confirmation_controller.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "User confirmation link is invalid or it has expired." msgid "User confirmation link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/index.ex:18
#, elixir-autogen, elixir-format
msgid "You are not authorized to view this page"
msgstr ""
#: lib/memex_web/controllers/user_auth.ex:177 #: lib/memex_web/controllers/user_auth.ex:177
#, 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."
@ -75,22 +59,22 @@ msgstr ""
msgid "You must confirm your account and log in to access this page." msgid "You must confirm your account and log in to access this page."
msgstr "" msgstr ""
#: lib/memex/accounts/user.ex:139 #: lib/memex/accounts/user.ex:144
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "did not change" msgid "did not change"
msgstr "" msgstr ""
#: lib/memex/accounts/user.ex:160 #: lib/memex/accounts/user.ex:165
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "does not match password" msgid "does not match password"
msgstr "" msgstr ""
#: lib/memex/accounts/user.ex:197 #: lib/memex/accounts/user.ex:202
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "is not valid" msgid "is not valid"
msgstr "" msgstr ""
#: lib/memex/accounts/user.ex:95 #: lib/memex/accounts/user.ex:99
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "must have the @ sign and no spaces" msgid "must have the @ sign and no spaces"
msgstr "" msgstr ""
@ -137,3 +121,28 @@ msgstr ""
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited" msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:13
#, elixir-autogen, elixir-format, fuzzy
msgid "oops, something went wrong! please check the errors below."
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:70
#, elixir-autogen, elixir-format, fuzzy
msgid "sorry, this invite was not found or expired"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:19
#, elixir-autogen, elixir-format, fuzzy
msgid "you are not authorized to view this page"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:84
#, elixir-autogen, elixir-format, fuzzy
msgid "email change link is invalid or it has expired."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:99
#, elixir-autogen, elixir-format, fuzzy
msgid "unable to delete user"
msgstr ""

View File

@ -15,56 +15,31 @@ msgstr ""
msgid "%{email} confirmed successfully." msgid "%{email} confirmed successfully."
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:62 #: lib/memex_web/live/invite_live/index.ex:54
#, elixir-autogen, elixir-format
msgid "%{invite_name} created successfully"
msgstr ""
#: lib/memex_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/memex_web/live/invite_live/index.ex:114 #: lib/memex_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/memex_web/live/invite_live/index.ex:90 #: lib/memex_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/memex_web/live/invite_live/index.ex:68 #: lib/memex_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/memex_web/live/invite_live/form_component.ex:42 #: lib/memex_web/live/invite_live/index.ex:140
#, elixir-autogen, elixir-format
msgid "%{invite_name} updated successfully"
msgstr ""
#: lib/memex_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 ""
#: lib/memex_web/controllers/user_settings_controller.ex:29
#, elixir-autogen, elixir-format
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:127
#, elixir-autogen, elixir-format
msgid "Copied to clipboard"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:77
#, elixir-autogen, elixir-format
msgid "Email changed successfully."
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:23 #: lib/memex_web/controllers/user_confirmation_controller.ex:23
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly." msgid "If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly."
@ -80,26 +55,11 @@ msgstr ""
msgid "Password reset successfully." msgid "Password reset successfully."
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:49
#, elixir-autogen, elixir-format
msgid "Password updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:73
#, elixir-autogen, elixir-format
msgid "Please check your email to verify your account"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:31 #: lib/memex_web/live/invite_live/form_component.html.heex:31
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Saving..." msgid "Saving..."
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:95
#, elixir-autogen, elixir-format
msgid "Your account has been deleted"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:136 #: lib/memex_web/templates/user_settings/edit.html.heex:136
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "are you sure you want to change your language?" msgid "are you sure you want to change your language?"
@ -156,3 +116,43 @@ msgstr ""
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "register to setup memEx" msgid "register to setup memEx"
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:80
#, elixir-autogen, elixir-format, fuzzy
msgid "%{name} created successfully"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:62
#, elixir-autogen, elixir-format, fuzzy
msgid "%{name} updated successfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:128
#, elixir-autogen, elixir-format, fuzzy
msgid "copied to clipboard"
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:65
#, elixir-autogen, elixir-format, fuzzy
msgid "please check your email to verify your account"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:29
#, elixir-autogen, elixir-format, fuzzy
msgid "a link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:77
#, elixir-autogen, elixir-format, fuzzy
msgid "email changed successfully."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:49
#, elixir-autogen, elixir-format, fuzzy
msgid "password updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:95
#, elixir-autogen, elixir-format, fuzzy
msgid "your account has been deleted"
msgstr ""

View File

@ -40,11 +40,6 @@ msgstr ""
msgid "Reset your password" msgid "Reset your password"
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:10
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:25 #: lib/memex_web/live/invite_live/form_component.html.heex:25
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Uses left" msgid "Uses left"
@ -121,7 +116,7 @@ msgstr ""
msgid "document your processes, attaching contexts to each step" msgid "document your processes, attaching contexts to each step"
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/index.ex:33 #: lib/memex_web/live/invite_live/index.ex:34
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "edit invite" msgid "edit invite"
msgstr "" msgstr ""
@ -141,7 +136,7 @@ msgstr ""
msgid "enable" msgid "enable"
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:34 #: lib/memex_web/templates/user_registration/new.html.heex:32
#: lib/memex_web/templates/user_settings/edit.html.heex:129 #: lib/memex_web/templates/user_settings/edit.html.heex:129
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "english" msgid "english"
@ -167,7 +162,7 @@ msgstr ""
msgid "instance information" msgid "instance information"
msgstr "" msgstr ""
#: lib/memex_web/components/invite_card.ex:32 #: lib/memex_web/components/invite_card.ex:42
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "invite disabled" msgid "invite disabled"
msgstr "" msgstr ""
@ -178,7 +173,7 @@ msgid "invite only"
msgstr "" msgstr ""
#: lib/memex_web/components/topbar.ex:73 #: lib/memex_web/components/topbar.ex:73
#: lib/memex_web/live/invite_live/index.ex:41 #: lib/memex_web/live/invite_live/index.ex:42
#: lib/memex_web/live/invite_live/index.html.heex:3 #: lib/memex_web/live/invite_live/index.html.heex:3
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "invites" msgid "invites"
@ -194,7 +189,7 @@ msgstr ""
msgid "multi-user:" msgid "multi-user:"
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/index.ex:37 #: lib/memex_web/live/invite_live/index.ex:38
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "new invite" msgid "new invite"
msgstr "" msgstr ""
@ -260,7 +255,7 @@ msgstr ""
msgid "public signups" msgid "public signups"
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:34 #: lib/memex_web/controllers/user_registration_controller.ex:32
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "register" msgid "register"
msgstr "" msgstr ""
@ -295,6 +290,7 @@ msgstr ""
msgid "set unlimited" msgid "set unlimited"
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:10
#: lib/memex_web/templates/user_settings/edit.html.heex:3 #: lib/memex_web/templates/user_settings/edit.html.heex:3
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "settings" msgid "settings"
@ -621,7 +617,7 @@ msgstr ""
msgid "while memEx fully supports multiple users, each memEx instance should be treated as a single cohesive and collaborative document." msgid "while memEx fully supports multiple users, each memEx instance should be treated as a single cohesive and collaborative document."
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:30 #: lib/memex_web/templates/user_registration/new.html.heex:28
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "language" msgid "language"
msgstr "" msgstr ""
@ -636,12 +632,7 @@ msgstr ""
msgid "user registered on%{registered_datetime}" msgid "user registered on%{registered_datetime}"
msgstr "" msgstr ""
#: lib/memex_web/components/invite_card.ex:22 #: lib/memex_web/components/invite_card.ex:37
#, elixir-autogen, elixir-format
msgid "uses left: %{uses_left}"
msgstr ""
#: lib/memex_web/components/invite_card.ex:27
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "uses left: unlimited" msgid "uses left: unlimited"
msgstr "" msgstr ""
@ -650,3 +641,13 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "read more on how to use memEx" msgid "read more on how to use memEx"
msgstr "" msgstr ""
#: lib/memex_web/components/invite_card.ex:32
#, elixir-autogen, elixir-format
msgid "uses left: %{uses_left_count}"
msgstr ""
#: lib/memex_web/components/invite_card.ex:52
#, elixir-autogen, elixir-format
msgid "uses: %{uses_count}"
msgstr ""

View File

@ -10,11 +10,6 @@
msgid "" msgid ""
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:84
#, elixir-autogen, elixir-format
msgid "Email change link is invalid or it has expired."
msgstr ""
#: lib/memex_web/templates/error/error.html.heex:8 #: lib/memex_web/templates/error/error.html.heex:8
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Error" msgid "Error"
@ -25,7 +20,6 @@ msgstr ""
msgid "Invalid email or password" msgid "Invalid email or password"
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:14
#: lib/memex_web/templates/user_reset_password/edit.html.heex:14 #: lib/memex_web/templates/user_reset_password/edit.html.heex:14
#: lib/memex_web/templates/user_settings/edit.html.heex:67 #: lib/memex_web/templates/user_settings/edit.html.heex:67
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -37,33 +31,23 @@ msgstr ""
msgid "Reset password link is invalid or it has expired." msgid "Reset password link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:24 #: lib/memex_web/controllers/user_registration_controller.ex:22
#: lib/memex_web/controllers/user_registration_controller.ex:55 #: lib/memex_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/memex_web/controllers/user_registration_controller.ex:14 #: lib/memex_web/controllers/user_registration_controller.ex:12
#: lib/memex_web/controllers/user_registration_controller.ex:45 #: lib/memex_web/controllers/user_registration_controller.ex:41
#, 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 ""
#: lib/memex_web/controllers/user_settings_controller.ex:99
#, elixir-autogen, elixir-format
msgid "Unable to delete user"
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:54 #: lib/memex_web/controllers/user_confirmation_controller.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "User confirmation link is invalid or it has expired." msgid "User confirmation link is invalid or it has expired."
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/index.ex:18
#, elixir-autogen, elixir-format
msgid "You are not authorized to view this page"
msgstr ""
#: lib/memex_web/controllers/user_auth.ex:177 #: lib/memex_web/controllers/user_auth.ex:177
#, 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."
@ -75,22 +59,22 @@ msgstr ""
msgid "You must confirm your account and log in to access this page." msgid "You must confirm your account and log in to access this page."
msgstr "" msgstr ""
#: lib/memex/accounts/user.ex:139 #: lib/memex/accounts/user.ex:144
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "did not change" msgid "did not change"
msgstr "" msgstr ""
#: lib/memex/accounts/user.ex:160 #: lib/memex/accounts/user.ex:165
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "does not match password" msgid "does not match password"
msgstr "" msgstr ""
#: lib/memex/accounts/user.ex:197 #: lib/memex/accounts/user.ex:202
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "is not valid" msgid "is not valid"
msgstr "" msgstr ""
#: lib/memex/accounts/user.ex:95 #: lib/memex/accounts/user.ex:99
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "must have the @ sign and no spaces" msgid "must have the @ sign and no spaces"
msgstr "" msgstr ""
@ -137,3 +121,28 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited" msgid "invalid format: only numbers, letters and hyphen are accepted. tags must be comma-delimited"
msgstr "" msgstr ""
#: lib/memex_web/templates/user_registration/new.html.heex:13
#, elixir-autogen, elixir-format
msgid "oops, something went wrong! please check the errors below."
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:70
#, elixir-autogen, elixir-format
msgid "sorry, this invite was not found or expired"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:19
#, elixir-autogen, elixir-format
msgid "you are not authorized to view this page"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:84
#, elixir-autogen, elixir-format
msgid "email change link is invalid or it has expired."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:99
#, elixir-autogen, elixir-format
msgid "unable to delete user"
msgstr ""

View File

@ -15,56 +15,31 @@ msgstr ""
msgid "%{email} confirmed successfully." msgid "%{email} confirmed successfully."
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:62 #: lib/memex_web/live/invite_live/index.ex:54
#, elixir-autogen, elixir-format
msgid "%{invite_name} created successfully"
msgstr ""
#: lib/memex_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/memex_web/live/invite_live/index.ex:114 #: lib/memex_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/memex_web/live/invite_live/index.ex:90 #: lib/memex_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/memex_web/live/invite_live/index.ex:68 #: lib/memex_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/memex_web/live/invite_live/form_component.ex:42 #: lib/memex_web/live/invite_live/index.ex:140
#, elixir-autogen, elixir-format
msgid "%{invite_name} updated successfully"
msgstr ""
#: lib/memex_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 ""
#: lib/memex_web/controllers/user_settings_controller.ex:29
#, elixir-autogen, elixir-format
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:127
#, elixir-autogen, elixir-format
msgid "Copied to clipboard"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:77
#, elixir-autogen, elixir-format
msgid "Email changed successfully."
msgstr ""
#: lib/memex_web/controllers/user_confirmation_controller.ex:23 #: lib/memex_web/controllers/user_confirmation_controller.ex:23
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly." msgid "If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly."
@ -80,26 +55,11 @@ msgstr ""
msgid "Password reset successfully." msgid "Password reset successfully."
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:49
#, elixir-autogen, elixir-format
msgid "Password updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:73
#, elixir-autogen, elixir-format
msgid "Please check your email to verify your account"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.html.heex:31 #: lib/memex_web/live/invite_live/form_component.html.heex:31
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Saving..." msgid "Saving..."
msgstr "" msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:95
#, elixir-autogen, elixir-format
msgid "Your account has been deleted"
msgstr ""
#: lib/memex_web/templates/user_settings/edit.html.heex:136 #: lib/memex_web/templates/user_settings/edit.html.heex:136
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "are you sure you want to change your language?" msgid "are you sure you want to change your language?"
@ -156,3 +116,43 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "register to setup memEx" msgid "register to setup memEx"
msgstr "" msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:80
#, elixir-autogen, elixir-format
msgid "%{name} created successfully"
msgstr ""
#: lib/memex_web/live/invite_live/form_component.ex:62
#, elixir-autogen, elixir-format
msgid "%{name} updated successfully"
msgstr ""
#: lib/memex_web/live/invite_live/index.ex:128
#, elixir-autogen, elixir-format
msgid "copied to clipboard"
msgstr ""
#: lib/memex_web/controllers/user_registration_controller.ex:65
#, elixir-autogen, elixir-format
msgid "please check your email to verify your account"
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:29
#, elixir-autogen, elixir-format
msgid "a link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:77
#, elixir-autogen, elixir-format
msgid "email changed successfully."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:49
#, elixir-autogen, elixir-format
msgid "password updated successfully."
msgstr ""
#: lib/memex_web/controllers/user_settings_controller.ex:95
#, elixir-autogen, elixir-format
msgid "your account has been deleted"
msgstr ""

View File

@ -0,0 +1,11 @@
defmodule Memex.Repo.Migrations.RecordInvites do
use Ecto.Migration
def change do
alter table(:users) do
add :invite_id, references(:invites, type: :binary_id)
end
rename table(:invites), :user_id, to: :created_by_id
end
end

View File

@ -0,0 +1,176 @@
defmodule Memex.InvitesTest do
@moduledoc """
This module tests the Memex.Accounts.Invites context
"""
use Memex.DataCase
alias Ecto.Changeset
alias Memex.Accounts
alias Memex.Accounts.{Invite, Invites}
@moduletag :invites_test
@valid_attrs %{
"name" => "some name"
}
@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 "get_use_count/2 returns the correct invite usage",
%{invite: %{token: token} = invite, current_user: current_user} do
assert 0 == Invites.get_use_count(invite, current_user)
assert {:ok, _user} =
Accounts.register_user(
%{"email" => unique_user_email(), "password" => valid_user_password()},
token
)
assert 1 == Invites.get_use_count(invite, current_user)
assert {:ok, _user} =
Accounts.register_user(
%{"email" => unique_user_email(), "password" => valid_user_password()},
token
)
assert 2 == Invites.get_use_count(invite, current_user)
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} = invite, current_user: current_user} do
{:ok, _invite} = Invites.update_invite(invite, %{uses_left: 10}, current_user)
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 an unlimited invite",
%{current_user: current_user} do
assert {:ok, %Invite{} = invite} =
Invites.create_invite(current_user, %{
"name" => "some name"
})
assert invite.name == "some name"
end
test "create_invite/1 with valid data creates a limited invite",
%{current_user: current_user} do
assert {:ok, %Invite{} = invite} =
Invites.create_invite(current_user, %{
"name" => "some name",
"uses_left" => 10
})
assert invite.name == "some name"
assert invite.uses_left == 10
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 can set an invite to be limited",
%{invite: invite, current_user: current_user} do
assert {:ok, %Invite{} = new_invite} =
Invites.update_invite(
invite,
%{"name" => "some updated name", "uses_left" => 5},
current_user
)
assert new_invite.name == "some updated name"
assert new_invite.uses_left == 5
end
test "update_invite/2 can set an invite to be unlimited",
%{invite: invite, current_user: current_user} do
{:ok, invite} = Invites.update_invite(invite, %{"uses_left" => 5}, current_user)
assert {:ok, %Invite{} = new_invite} =
Invites.update_invite(
invite,
%{"name" => "some updated name", "uses_left" => nil},
current_user
)
assert new_invite.name == "some updated name"
assert new_invite.uses_left |> is_nil()
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

@ -6,7 +6,7 @@ defmodule Memex.AccountsTest do
use Memex.DataCase use Memex.DataCase
alias Ecto.Changeset alias Ecto.Changeset
alias Memex.Accounts alias Memex.Accounts
alias Memex.Accounts.{User, UserToken} alias Memex.Accounts.{Invites, User, UserToken}
@moduletag :accounts_test @moduletag :accounts_test
@ -102,6 +102,17 @@ defmodule Memex.AccountsTest do
assert is_nil(user.confirmed_at) assert is_nil(user.confirmed_at)
assert is_nil(user.password) assert is_nil(user.password)
end end
test "records used invite during registration" do
{:ok, %{id: invite_id, token: token}} =
admin_fixture() |> Invites.create_invite(%{"name" => "my invite"})
assert {:ok, %{invite_id: ^invite_id}} =
Accounts.register_user(
%{"email" => unique_user_email(), "password" => valid_user_password()},
token
)
end
end end
describe "change_user_registration/2" do describe "change_user_registration/2" do

View File

@ -1,76 +0,0 @@
defmodule Memex.InvitesTest do
@moduledoc """
This module tests the Invites context
"""
use Memex.DataCase
alias Ecto.Changeset
alias Memex.{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

@ -8,14 +8,7 @@ defmodule MemexWeb.ExportControllerTest do
@moduletag :export_controller_test @moduletag :export_controller_test
setup %{conn: conn} do setup [:register_and_log_in_user]
current_user = user_fixture() |> confirm_user()
[
current_user: current_user,
conn: conn |> log_in_user(current_user)
]
end
defp add_data(%{current_user: current_user}) do defp add_data(%{current_user: current_user}) do
note = note_fixture(current_user) note = note_fixture(current_user)

View File

@ -33,7 +33,7 @@ defmodule MemexWeb.UserRegistrationControllerTest do
}) })
assert get_session(conn, :phoenix_flash) == %{ assert get_session(conn, :phoenix_flash) == %{
"info" => dgettext("prompts", "Please check your email to verify your account") "info" => dgettext("prompts", "please check your email to verify your account")
} }
assert redirected_to(conn) =~ "/" assert redirected_to(conn) =~ "/"

View File

@ -15,7 +15,7 @@ defmodule MemexWeb.UserSettingsControllerTest do
test "renders settings page", %{conn: conn} do test "renders settings page", %{conn: conn} do
conn = get(conn, Routes.user_settings_path(conn, :edit)) conn = get(conn, Routes.user_settings_path(conn, :edit))
response = html_response(conn, 200) response = html_response(conn, 200)
assert response =~ gettext("Settings") assert response =~ gettext("settings")
end end
test "redirects if user is not logged in" do test "redirects if user is not logged in" do
@ -27,7 +27,7 @@ defmodule MemexWeb.UserSettingsControllerTest do
describe "PUT /users/settings (change password form)" do describe "PUT /users/settings (change password form)" do
test "updates the user password and resets tokens", test "updates the user password and resets tokens",
%{conn: conn, user: user} do %{conn: conn, current_user: current_user} do
new_password_conn = new_password_conn =
put(conn, Routes.user_settings_path(conn, :update), %{ put(conn, Routes.user_settings_path(conn, :update), %{
"action" => "update_password", "action" => "update_password",
@ -42,9 +42,9 @@ defmodule MemexWeb.UserSettingsControllerTest do
assert get_session(new_password_conn, :user_token) != get_session(conn, :user_token) assert get_session(new_password_conn, :user_token) != get_session(conn, :user_token)
assert get_flash(new_password_conn, :info) =~ assert get_flash(new_password_conn, :info) =~
dgettext("actions", "Password updated successfully") dgettext("actions", "password updated successfully")
assert Accounts.get_user_by_email_and_password(user.email, "new valid password") assert Accounts.get_user_by_email_and_password(current_user.email, "new valid password")
end end
test "does not update password on invalid data", %{conn: conn} do test "does not update password on invalid data", %{conn: conn} do
@ -70,7 +70,7 @@ defmodule MemexWeb.UserSettingsControllerTest do
describe "PUT /users/settings (change email form)" do describe "PUT /users/settings (change email form)" do
@tag :capture_log @tag :capture_log
test "updates the user email", %{conn: conn, user: user} do test "updates the user email", %{conn: conn, current_user: current_user} do
conn = conn =
put(conn, Routes.user_settings_path(conn, :update), %{ put(conn, Routes.user_settings_path(conn, :update), %{
"action" => "update_email", "action" => "update_email",
@ -83,10 +83,10 @@ defmodule MemexWeb.UserSettingsControllerTest do
assert get_flash(conn, :info) =~ assert get_flash(conn, :info) =~
dgettext( dgettext(
"prompts", "prompts",
"A link to confirm your email change has been sent to the new address." "a link to confirm your email change has been sent to the new address."
) )
assert Accounts.get_user_by_email(user.email) assert Accounts.get_user_by_email(current_user.email)
end end
test "does not update email on invalid data", %{conn: conn} do test "does not update email on invalid data", %{conn: conn} do
@ -105,14 +105,14 @@ defmodule MemexWeb.UserSettingsControllerTest do
end end
describe "GET /users/settings/confirm_email/:token" do describe "GET /users/settings/confirm_email/:token" do
setup %{user: user} do setup %{current_user: current_user} do
email = unique_user_email() email = unique_user_email()
token = token =
extract_user_token(fn url -> extract_user_token(fn url ->
Accounts.deliver_update_email_instructions( Accounts.deliver_update_email_instructions(
%{user | email: email}, %{current_user | email: email},
user.email, current_user.email,
url url
) )
end) end)
@ -121,28 +121,28 @@ defmodule MemexWeb.UserSettingsControllerTest do
end end
test "updates the user email once", test "updates the user email once",
%{conn: conn, user: user, token: token, email: email} do %{conn: conn, current_user: current_user, token: token, email: email} do
conn = get(conn, Routes.user_settings_path(conn, :confirm_email, token)) conn = get(conn, Routes.user_settings_path(conn, :confirm_email, token))
assert redirected_to(conn) == Routes.user_settings_path(conn, :edit) assert redirected_to(conn) == Routes.user_settings_path(conn, :edit)
assert get_flash(conn, :info) =~ dgettext("prompts", "Email changed successfully") assert get_flash(conn, :info) =~ dgettext("prompts", "email changed successfully")
refute Accounts.get_user_by_email(user.email) refute Accounts.get_user_by_email(current_user.email)
assert Accounts.get_user_by_email(email) assert Accounts.get_user_by_email(email)
conn = get(conn, Routes.user_settings_path(conn, :confirm_email, token)) conn = get(conn, Routes.user_settings_path(conn, :confirm_email, token))
assert redirected_to(conn) == Routes.user_settings_path(conn, :edit) assert redirected_to(conn) == Routes.user_settings_path(conn, :edit)
assert get_flash(conn, :error) =~ assert get_flash(conn, :error) =~
dgettext("errors", "Email change link is invalid or it has expired") dgettext("errors", "email change link is invalid or it has expired")
end end
test "does not update email with invalid token", %{conn: conn, user: user} do test "does not update email with invalid token", %{conn: conn, current_user: current_user} do
conn = get(conn, Routes.user_settings_path(conn, :confirm_email, "oops")) conn = get(conn, Routes.user_settings_path(conn, :confirm_email, "oops"))
assert redirected_to(conn) == Routes.user_settings_path(conn, :edit) assert redirected_to(conn) == Routes.user_settings_path(conn, :edit)
assert get_flash(conn, :error) =~ assert get_flash(conn, :error) =~
dgettext("errors", "Email change link is invalid or it has expired") dgettext("errors", "email change link is invalid or it has expired")
assert Accounts.get_user_by_email(user.email) assert Accounts.get_user_by_email(current_user.email)
end end
test "redirects if user is not logged in", %{token: token} do test "redirects if user is not logged in", %{token: token} do

View File

@ -23,8 +23,8 @@ defmodule MemexWeb.ContextLiveTest do
"visibility" => nil "visibility" => nil
} }
defp create_context(%{user: user}) do defp create_context(%{current_user: current_user}) do
[context: context_fixture(user)] [context: context_fixture(current_user)]
end end
describe "Index" do describe "Index" do
@ -148,13 +148,16 @@ defmodule MemexWeb.ContextLiveTest do
describe "show with note" do describe "show with note" do
setup [:register_and_log_in_user] setup [:register_and_log_in_user]
setup %{user: user} do setup %{current_user: current_user} do
%{slug: note_slug} = note = note_fixture(user) %{slug: note_slug} = note = note_fixture(current_user)
[ [
note: note, note: note,
context: context:
context_fixture(%{content: "example with backlink to [[#{note_slug}]] note"}, user) context_fixture(
%{content: "example with backlink to [[#{note_slug}]] note"},
current_user
)
] ]
end end

View File

@ -6,7 +6,7 @@ defmodule MemexWeb.InviteLiveTest do
use MemexWeb.ConnCase use MemexWeb.ConnCase
import Phoenix.LiveViewTest import Phoenix.LiveViewTest
import MemexWeb.Gettext import MemexWeb.Gettext
alias Memex.Invites alias Memex.Accounts.Invites
@moduletag :invite_live_test @moduletag :invite_live_test
@create_attrs %{"name" => "some name"} @create_attrs %{"name" => "some name"}
@ -16,9 +16,9 @@ defmodule MemexWeb.InviteLiveTest do
describe "Index" do describe "Index" do
setup [:register_and_log_in_user] setup [:register_and_log_in_user]
setup %{user: user} do setup %{current_user: current_user} do
{:ok, invite} = Invites.create_invite(user, @create_attrs) {:ok, invite} = Invites.create_invite(current_user, @create_attrs)
%{invite: invite, user: user} %{invite: invite, current_user: current_user}
end end
test "lists all invites", %{conn: conn, invite: invite} do test "lists all invites", %{conn: conn, invite: invite} do

View File

@ -24,8 +24,8 @@ defmodule MemexWeb.NoteLiveTest do
"visibility" => nil "visibility" => nil
} }
defp create_note(%{user: user}) do defp create_note(%{current_user: current_user}) do
[note: note_fixture(user)] [note: note_fixture(current_user)]
end end
describe "Index" do describe "Index" do
@ -149,13 +149,13 @@ defmodule MemexWeb.NoteLiveTest do
describe "show with note" do describe "show with note" do
setup [:register_and_log_in_user] setup [:register_and_log_in_user]
setup %{user: user} do setup %{current_user: current_user} do
%{slug: note_slug} = note = note_fixture(user) %{slug: note_slug} = note = note_fixture(current_user)
[ [
note: note, note: note,
backlinked_note: backlinked_note:
note_fixture(%{content: "example with backlink to [[#{note_slug}]] note"}, user) note_fixture(%{content: "example with backlink to [[#{note_slug}]] note"}, current_user)
] ]
end end

View File

@ -34,8 +34,8 @@ defmodule MemexWeb.PipelineLiveTest do
"title" => nil "title" => nil
} }
defp create_pipeline(%{user: user}) do defp create_pipeline(%{current_user: current_user}) do
[pipeline: pipeline_fixture(user)] [pipeline: pipeline_fixture(current_user)]
end end
describe "Index" do describe "Index" do
@ -181,9 +181,9 @@ defmodule MemexWeb.PipelineLiveTest do
describe "show with a step" do describe "show with a step" do
setup [:register_and_log_in_user, :create_pipeline] setup [:register_and_log_in_user, :create_pipeline]
setup %{pipeline: pipeline, user: user} do setup %{pipeline: pipeline, current_user: current_user} do
[ [
step: step_fixture(0, pipeline, user) step: step_fixture(0, pipeline, current_user)
] ]
end end
@ -236,11 +236,11 @@ defmodule MemexWeb.PipelineLiveTest do
describe "show with multiple steps" do describe "show with multiple steps" do
setup [:register_and_log_in_user, :create_pipeline] setup [:register_and_log_in_user, :create_pipeline]
setup %{pipeline: pipeline, user: user} do setup %{pipeline: pipeline, current_user: current_user} do
[ [
first_step: step_fixture(%{title: "first step"}, 0, pipeline, user), first_step: step_fixture(%{title: "first step"}, 0, pipeline, current_user),
second_step: step_fixture(%{title: "second step"}, 1, pipeline, user), second_step: step_fixture(%{title: "second step"}, 1, pipeline, current_user),
third_step: step_fixture(%{title: "third step"}, 2, pipeline, user) third_step: step_fixture(%{title: "third step"}, 2, pipeline, current_user)
] ]
end end

View File

@ -23,10 +23,11 @@ defmodule MemexWeb.ConnCase do
using do using do
quote do quote do
# Import conveniences for testing with connections # Import conveniences for testing with connections
import Plug.Conn # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
import Phoenix.ConnTest
import Memex.Fixtures import Memex.Fixtures
import MemexWeb.ConnCase import MemexWeb.ConnCase
import Phoenix.ConnTest
import Plug.Conn
alias MemexWeb.Router.Helpers, as: Routes alias MemexWeb.Router.Helpers, as: Routes
@ -50,10 +51,10 @@ defmodule MemexWeb.ConnCase do
test context. test context.
""" """
@spec register_and_log_in_user(%{conn: Plug.Conn.t()}) :: @spec register_and_log_in_user(%{conn: Plug.Conn.t()}) ::
%{conn: Plug.Conn.t(), user: User.t()} %{conn: Plug.Conn.t(), current_user: User.t()}
def register_and_log_in_user(%{conn: conn}) do def register_and_log_in_user(%{conn: conn}) do
user = user_fixture() |> confirm_user() current_user = user_fixture() |> confirm_user()
%{conn: log_in_user(conn, user), user: user} %{conn: log_in_user(conn, current_user), current_user: current_user}
end end
@spec confirm_user(User.t()) :: User.t() @spec confirm_user(User.t()) :: User.t()

View File

@ -22,10 +22,8 @@ defmodule Memex.DataCase do
alias Memex.Repo alias Memex.Repo
import Ecto import Ecto
import Ecto.Changeset import Ecto.{Changeset, Query}
import Ecto.Query import Memex.{DataCase, Fixtures}
import Memex.DataCase
import Memex.Fixtures
end end
end end
@ -45,7 +43,7 @@ defmodule Memex.DataCase do
""" """
def errors_on(changeset) do def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Regex.replace(~r"%{(\w+)}", message, fn _, key -> Regex.replace(~r"%{(\w+)}", message, fn _capture, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end) end)
end) end)