fix n+1 queries with invite card
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		| @@ -100,13 +100,23 @@ defmodule Memex.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( | ||||
|   @spec get_use_count(Invite.t(), User.t()) :: non_neg_integer() | nil | ||||
|   def get_use_count(%Invite{id: invite_id} = invite, user) do | ||||
|     [invite] |> get_use_counts(user) |> Map.get(invite_id) | ||||
|   end | ||||
|  | ||||
|   @spec get_use_counts([Invite.t()], User.t()) :: | ||||
|           %{optional(Invite.id()) => non_neg_integer()} | ||||
|   def get_use_counts(invites, %User{role: :admin}) do | ||||
|     invite_ids = invites |> Enum.map(fn %{id: invite_id} -> invite_id end) | ||||
|  | ||||
|     Repo.all( | ||||
|       from u in User, | ||||
|         where: u.invite_id == ^invite_id, | ||||
|         select: count(u.id) | ||||
|         where: u.invite_id in ^invite_ids, | ||||
|         group_by: u.invite_id, | ||||
|         select: {u.invite_id, count(u.id)} | ||||
|     ) | ||||
|     |> Map.new() | ||||
|   end | ||||
|  | ||||
|   @spec decrement_invite_changeset(Invite.t()) :: Invite.changeset() | ||||
|   | ||||
| @@ -4,7 +4,7 @@ defmodule MemexWeb.CoreComponents do | ||||
|   """ | ||||
|   use Phoenix.Component | ||||
|   import MemexWeb.{Gettext, ViewHelpers} | ||||
|   alias Memex.{Accounts, Accounts.Invite, Accounts.Invites, Accounts.User} | ||||
|   alias Memex.{Accounts, Accounts.Invite, Accounts.User} | ||||
|   alias Memex.Contexts.Context | ||||
|   alias Memex.Notes.Note | ||||
|   alias Memex.Pipelines.Steps.Step | ||||
| @@ -77,64 +77,12 @@ defmodule MemexWeb.CoreComponents do | ||||
|   def user_card(assigns) | ||||
|  | ||||
|   attr :invite, Invite, required: true | ||||
|   attr :use_count, :integer, default: nil | ||||
|   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)) | ||||
|  | ||||
|     ~H""" | ||||
|     <div class="px-8 py-4 flex flex-col justify-center items-center space-y-4 | ||||
|       bg-primary-900 | ||||
|       border border-gray-400 rounded-lg shadow-lg hover:shadow-md | ||||
|       transition-all duration-300 ease-in-out"> | ||||
|       <h1 class="title text-xl"> | ||||
|         <%= @invite.name %> | ||||
|       </h1> | ||||
|  | ||||
|       <%= if @invite.disabled_at |> is_nil() do %> | ||||
|         <h2 class="title text-md"> | ||||
|           <%= if @invite.uses_left do %> | ||||
|             <%= gettext( | ||||
|               "uses left: %{uses_left_count}", | ||||
|               uses_left_count: @invite.uses_left | ||||
|             ) %> | ||||
|           <% else %> | ||||
|             <%= gettext("uses left: unlimited") %> | ||||
|           <% end %> | ||||
|         </h2> | ||||
|       <% else %> | ||||
|         <h2 class="title text-md"> | ||||
|           <%= gettext("invite disabled") %> | ||||
|         </h2> | ||||
|       <% end %> | ||||
|  | ||||
|       <.qr_code | ||||
|         content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)} | ||||
|         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}"} | ||||
|           class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all | ||||
|             text-primary-400 bg-primary-800" | ||||
|           phx-no-format | ||||
|         ><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code> | ||||
|         <%= if @code_actions, do: render_slot(@code_actions) %> | ||||
|       </div> | ||||
|  | ||||
|       <div :if={@inner_block} class="flex space-x-4 justify-center items-center"> | ||||
|         <%= render_slot(@inner_block) %> | ||||
|       </div> | ||||
|     </div> | ||||
|     """ | ||||
|   end | ||||
|   def invite_card(assigns) | ||||
|  | ||||
|   attr :id, :string, required: true | ||||
|   attr :datetime, :any, required: true, doc: "A `DateTime` struct or nil" | ||||
|   | ||||
| @@ -0,0 +1,48 @@ | ||||
| <div class="px-8 py-4 flex flex-col justify-center items-center space-y-4 | ||||
|   bg-primary-900 | ||||
|   border border-gray-400 rounded-lg shadow-lg hover:shadow-md | ||||
|   transition-all duration-300 ease-in-out"> | ||||
|   <h1 class="title text-xl"> | ||||
|     <%= @invite.name %> | ||||
|   </h1> | ||||
|  | ||||
|   <%= if @invite.disabled_at |> is_nil() do %> | ||||
|     <h2 class="title text-md"> | ||||
|       <%= if @invite.uses_left do %> | ||||
|         <%= gettext( | ||||
|           "uses left: %{uses_left_count}", | ||||
|           uses_left_count: @invite.uses_left | ||||
|         ) %> | ||||
|       <% else %> | ||||
|         <%= gettext("uses left: unlimited") %> | ||||
|       <% end %> | ||||
|     </h2> | ||||
|   <% else %> | ||||
|     <h2 class="title text-md"> | ||||
|       <%= gettext("invite disabled") %> | ||||
|     </h2> | ||||
|   <% end %> | ||||
|  | ||||
|   <.qr_code | ||||
|     content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)} | ||||
|     filename={@invite.name} | ||||
|   /> | ||||
|  | ||||
|   <h2 :if={@use_count && @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}"} | ||||
|       class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all | ||||
|         text-primary-400 bg-primary-800" | ||||
|       phx-no-format | ||||
|     ><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code> | ||||
|     <%= if @code_actions, do: render_slot(@code_actions) %> | ||||
|   </div> | ||||
|  | ||||
|   <div :if={@inner_block} class="flex space-x-4 justify-center items-center"> | ||||
|     <%= render_slot(@inner_block) %> | ||||
|   </div> | ||||
| </div> | ||||
| @@ -148,7 +148,9 @@ defmodule MemexWeb.InviteLive.Index do | ||||
|       |> Map.get(:admin, []) | ||||
|       |> Enum.reject(fn %{id: user_id} -> user_id == current_user.id end) | ||||
|  | ||||
|     use_counts = invites |> Invites.get_use_counts(current_user) | ||||
|     users = all_users |> Map.get(:user, []) | ||||
|     socket |> assign(invites: invites, admins: admins, users: users) | ||||
|  | ||||
|     socket |> assign(invites: invites, use_counts: use_counts, admins: admins, users: users) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -14,7 +14,12 @@ | ||||
|   <% end %> | ||||
|  | ||||
|   <div class="flex flex-col justify-center items-stretch space-y-4"> | ||||
|     <.invite_card :for={invite <- @invites} invite={invite} current_user={@current_user}> | ||||
|     <.invite_card | ||||
|       :for={invite <- @invites} | ||||
|       invite={invite} | ||||
|       current_user={@current_user} | ||||
|       use_count={Map.get(@use_counts, invite.id)} | ||||
|     > | ||||
|       <:code_actions> | ||||
|         <form phx-submit="copy_to_clipboard"> | ||||
|           <button | ||||
|   | ||||
		Reference in New Issue
	
	Block a user