diff --git a/lib/microwaveprop_web/live/contact_live/index.ex b/lib/microwaveprop_web/live/contact_live/index.ex index 8cb15e1a..0f4e82f7 100644 --- a/lib/microwaveprop_web/live/contact_live/index.ex +++ b/lib/microwaveprop_web/live/contact_live/index.ex @@ -61,9 +61,31 @@ defmodule MicrowavepropWeb.ContactLive.Index do socket |> assign(:page_title, "Contacts") |> assign(:total_contacts, total) + |> assign(:visible_fields, visible_fields_for(scope)) |> assign(:data_provider, {__MODULE__, :visible_query_provider, [scope_token(scope)]})} end + # Hide the Private column entirely unless this scope can actually see + # at least one private contact — avoids an always-empty column for + # visitors and owners who've never flagged one as private. + defp visible_fields_for(scope) do + if has_viewable_private?(scope) do + fields() + else + Keyword.delete(fields(), :private) + end + end + + defp has_viewable_private?(%Scope{user: %User{is_admin: true}}) do + Repo.exists?(from(c in Contact, where: c.private == true)) + end + + defp has_viewable_private?(%Scope{user: %User{id: user_id}}) do + Repo.exists?(from(c in Contact, where: c.private == true and c.user_id == ^user_id)) + end + + defp has_viewable_private?(_), do: false + @doc false # Called by LiveTable to get the base queryable. Must be a # named function because the helper persists MFA across renders. @@ -183,7 +205,7 @@ defmodule MicrowavepropWeb.ContactLive.Index do <.live_table - fields={fields()} + fields={@visible_fields} filters={filters()} options={@options} streams={@streams} diff --git a/test/microwaveprop_web/live/contact_live_test.exs b/test/microwaveprop_web/live/contact_live_test.exs index 886a8ec7..4348c66b 100644 --- a/test/microwaveprop_web/live/contact_live_test.exs +++ b/test/microwaveprop_web/live/contact_live_test.exs @@ -167,6 +167,62 @@ defmodule MicrowavepropWeb.ContactLiveTest do end end + describe "Index private column visibility" do + import Microwaveprop.AccountsFixtures + + test "column hidden for anonymous viewer when no private contacts exist", %{conn: conn} do + _public = create_contact(%{station1: "W5PUB"}) + + {:ok, _lv, html} = live(conn, ~p"/contacts") + refute html =~ ">Private<" + end + + test "column hidden for anonymous viewer even when others have private contacts", %{conn: conn} do + _private = create_contact(%{station1: "W5PRV", private: true}) + _public = create_contact(%{station1: "W5PUB"}) + + {:ok, _lv, html} = live(conn, ~p"/contacts") + refute html =~ ">Private<" + end + + test "column hidden for a user who has no private contacts of their own", %{conn: conn} do + owner = user_fixture() + viewer = user_fixture() + _others_private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id}) + conn = log_in_user(conn, viewer) + + {:ok, _lv, html} = live(conn, ~p"/contacts") + refute html =~ ">Private<" + end + + test "column visible for a user with at least one of their own private contacts", %{conn: conn} do + owner = user_fixture() + _private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id}) + conn = log_in_user(conn, owner) + + {:ok, _lv, html} = live(conn, ~p"/contacts") + assert html =~ ">Private<" + end + + test "column hidden for admin when there are no private contacts anywhere", %{conn: conn} do + admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!() + _public = create_contact(%{station1: "W5PUB"}) + conn = log_in_user(conn, admin) + + {:ok, _lv, html} = live(conn, ~p"/contacts") + refute html =~ ">Private<" + end + + test "column visible for admin as soon as any private contact exists", %{conn: conn} do + admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!() + _private = create_contact(%{station1: "W5PRV", private: true}) + conn = log_in_user(conn, admin) + + {:ok, _lv, html} = live(conn, ~p"/contacts") + assert html =~ ">Private<" + end + end + describe "Show private visibility" do import Microwaveprop.AccountsFixtures