record invites

This commit is contained in:
2023-02-04 16:11:58 -05:00
committed by oliviasculley
parent ed8c20e967
commit 30d3f76fe1
13 changed files with 171 additions and 56 deletions

View File

@ -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

View File

@ -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

View File

@ -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(%{})

View File

@ -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

View File

@ -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"""
<div class="mx-4 my-2 px-8 py-4 flex flex-col justify-center items-center space-y-4
@ -21,8 +30,8 @@ defmodule LokalWeb.Components.InviteCard do
<h2 class="title text-md">
<%= if @invite.uses_left do %>
<%= gettext(
"Uses Left: %{uses_left}",
uses_left: @invite.uses_left
"Uses Left: %{uses_left_count}",
uses_left_count: @invite.uses_left
) %>
<% else %>
<%= gettext("Uses Left: Unlimited") %>
@ -39,6 +48,10 @@ defmodule LokalWeb.Components.InviteCard do
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">
<code
id={"code-#{@invite.id}"}

View File

@ -18,7 +18,7 @@
<% end %>
<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>
<form phx-submit="copy_to_clipboard">
<button