improve invites, record usage
This commit is contained in:
		| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
							
								
								
									
										196
									
								
								lib/memex/accounts/invites.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								lib/memex/accounts/invites.ex
									
									
									
									
									
										Normal 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 | ||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| @@ -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}"} | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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} -> | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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") %> | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 "" | ||||||
|   | |||||||
| @@ -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 "" | ||||||
|   | |||||||
| @@ -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 "" | ||||||
|   | |||||||
| @@ -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 "" | ||||||
|   | |||||||
| @@ -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 "" | ||||||
|   | |||||||
| @@ -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 "" | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								priv/repo/migrations/20230204191547_record_invites.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								priv/repo/migrations/20230204191547_record_invites.exs
									
									
									
									
									
										Normal 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 | ||||||
							
								
								
									
										176
									
								
								test/memex/accounts/invites_text.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								test/memex/accounts/invites_text.exs
									
									
									
									
									
										Normal 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 | ||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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) =~ "/" | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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() | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user