diff --git a/lib/lokal/accounts.ex b/lib/lokal/accounts.ex index e3926130..53bd7521 100644 --- a/lib/lokal/accounts.ex +++ b/lib/lokal/accounts.ex @@ -124,16 +124,15 @@ defmodule Lokal.Accounts do |> Multi.one(:users_count, from(u in User, select: count(u.id), distinct: true)) |> Multi.run(:use_invite, fn _changes_so_far, _repo -> if allow_registration?() and invite_token |> is_nil() do - {:ok, :invite_not_required} + {:ok, nil} else Invites.use_invite(invite_token) end end) - |> Multi.insert(:add_user, fn %{users_count: count} -> + |> Multi.insert(:add_user, fn %{users_count: count, use_invite: invite} -> # if no registered users, make first user an admin role = if count == 0, do: :admin, else: :user - - User.registration_changeset(attrs) |> User.role_changeset(role) + User.registration_changeset(attrs, invite) |> User.role_changeset(role) end) |> Repo.transaction() |> case do @@ -158,7 +157,7 @@ defmodule Lokal.Accounts do @spec change_user_registration() :: User.changeset() @spec change_user_registration(attrs :: map()) :: User.changeset() def change_user_registration(attrs \\ %{}) do - User.registration_changeset(attrs, hash_password: false) + User.registration_changeset(attrs, nil, hash_password: false) end ## Settings diff --git a/lib/lokal/accounts/invite.ex b/lib/lokal/accounts/invite.ex index 00c15aed..35951a8d 100644 --- a/lib/lokal/accounts/invite.ex +++ b/lib/lokal/accounts/invite.ex @@ -7,7 +7,7 @@ defmodule Lokal.Accounts.Invite do use Ecto.Schema import Ecto.Changeset - alias Ecto.{Changeset, UUID} + alias Ecto.{Association, Changeset, UUID} alias Lokal.Accounts.User @primary_key {:id, :binary_id, autogenerate: true} @@ -18,7 +18,9 @@ defmodule Lokal.Accounts.Invite do field :uses_left, :integer, default: nil field :disabled_at, :naive_datetime - belongs_to :user, User + belongs_to :created_by, User + + has_many :users, User timestamps() end @@ -29,8 +31,9 @@ defmodule Lokal.Accounts.Invite do token: token(), uses_left: integer() | nil, disabled_at: NaiveDateTime.t(), - user: User.t(), - user_id: User.id(), + created_by: User.t() | nil | Association.NotLoaded.t(), + created_by_id: User.id() | nil, + users: [User.t()] | Association.NotLoaded.t(), inserted_at: NaiveDateTime.t(), updated_at: NaiveDateTime.t() } @@ -43,9 +46,9 @@ defmodule Lokal.Accounts.Invite do @spec create_changeset(User.t(), token(), attrs :: map()) :: changeset() def create_changeset(%User{id: user_id}, token, attrs) do %__MODULE__{} - |> change(token: token, user_id: user_id) + |> change(token: token, created_by_id: user_id) |> cast(attrs, [:name, :uses_left, :disabled_at]) - |> validate_required([:name, :token, :user_id]) + |> validate_required([:name, :token, :created_by_id]) |> validate_number(:uses_left, greater_than_or_equal_to: 0) end diff --git a/lib/lokal/accounts/invites.ex b/lib/lokal/accounts/invites.ex index 71e953f2..0368fc83 100644 --- a/lib/lokal/accounts/invites.ex +++ b/lib/lokal/accounts/invites.ex @@ -98,6 +98,15 @@ defmodule Lokal.Accounts.Invites do 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(%{}) diff --git a/lib/lokal/accounts/user.ex b/lib/lokal/accounts/user.ex index e05a090f..8b31bc90 100644 --- a/lib/lokal/accounts/user.ex +++ b/lib/lokal/accounts/user.ex @@ -6,7 +6,7 @@ defmodule Lokal.Accounts.User do use Ecto.Schema import Ecto.Changeset import LokalWeb.Gettext - alias Ecto.{Changeset, UUID} + alias Ecto.{Association, Changeset, UUID} alias Lokal.Accounts.{Invite, User} @derive {Jason.Encoder, @@ -28,7 +28,9 @@ defmodule Lokal.Accounts.User do field :role, Ecto.Enum, values: [:admin, :user], default: :user 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() end @@ -41,7 +43,9 @@ defmodule Lokal.Accounts.User do confirmed_at: NaiveDateTime.t(), role: role(), locale: String.t() | nil, - invites: [Invite.t()], + created_invites: [Invite.t()] | Association.NotLoaded.t(), + invite: Invite.t() | nil | Association.NotLoaded.t(), + invite_id: Invite.id() | nil, inserted_at: NaiveDateTime.t(), updated_at: NaiveDateTime.t() } @@ -67,11 +71,12 @@ defmodule Lokal.Accounts.User do validations on a LiveView form), this option can be set to `false`. Defaults to `true`. """ - @spec registration_changeset(attrs :: map()) :: changeset() - @spec registration_changeset(attrs :: map(), opts :: keyword()) :: changeset() - def registration_changeset(attrs, opts \\ []) do + @spec registration_changeset(attrs :: map(), Invite.t() | nil) :: changeset() + @spec registration_changeset(attrs :: map(), Invite.t() | nil, opts :: keyword()) :: changeset() + def registration_changeset(attrs, invite, opts \\ []) do %User{} |> cast(attrs, [:email, :password, :locale]) + |> put_change(:invite_id, if(invite, do: invite.id)) |> validate_email() |> validate_password(opts) end diff --git a/lib/lokal_web/components/invite_card.ex b/lib/lokal_web/components/invite_card.ex index 8a58a7db..6dd0af67 100644 --- a/lib/lokal_web/components/invite_card.ex +++ b/lib/lokal_web/components/invite_card.ex @@ -4,10 +4,19 @@ defmodule LokalWeb.Components.InviteCard do """ use LokalWeb, :component + alias Lokal.Accounts.{Invite, Invites, User} alias LokalWeb.Endpoint - def invite_card(assigns) do - assigns = assigns |> assign_new(:code_actions, fn -> [] end) + attr :invite, Invite, required: true + 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"""
- <.invite_card :for={invite <- @invites} invite={invite}>
+ <.invite_card :for={invite <- @invites} invite={invite} current_user={@current_user}>
<:code_actions>