Plani: the plani pages only reach the user's own

f2c072f · Gabe Morell · 2026-11-04 22:10

14 files +71 -6
Message
{commit_body(@commit)}

Files changed

modified apps/room_sanctum/lib/room_sanctum/configuration.ex
+9 −0
@@ -709,6 +709,15 @@ defmodule RoomSanctum.Configuration do
709 709
710 710 def get_plani!(id), do: Repo.get!(Plani, id)
711 711
712 + # Someone else's plani raises the not-found a missing one -- or a junk id --
713 + # does.
714 + def get_plani!({:user, uid}, id) do
715 + case Integer.parse(to_string(id)) do
716 + {n, ""} -> Repo.get_by!(Plani, id: n, user_id: uid)
717 + _ -> raise Ecto.NoResultsError, queryable: Plani
718 + end
719 + end
720 +
712 721 def create_plani(attrs \\ %{}) do
713 722 %Plani{} |> Plani.changeset(attrs) |> Repo.insert() |> announce(:plani)
714 723 end
modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_plani_live/index.ex
+5 −2
@@ -17,7 +17,7 @@ defmodule RoomSanctumWeb.PlaniLive.Index do
17 17 defp apply_action(socket, :edit, %{"id" => id}) do
18 18 socket
19 19 |> assign(:page_title, "Modify Plani")
20 − |> assign(:plani, Configuration.get_plani!(id))
20 + |> assign(:plani, Configuration.get_plani!(owner(socket), id))
21 21 end
22 22
23 23 defp apply_action(socket, :new, _params) do
@@ -39,9 +39,12 @@ defmodule RoomSanctumWeb.PlaniLive.Index do
39 39
40 40 @impl true
41 41 def handle_event("delete", %{"id" => id}, socket) do
42 − plani = Configuration.get_plani!(id)
42 + plani = Configuration.get_plani!(owner(socket), id)
43 43 {:ok, _} = Configuration.delete_plani(plani)
44 44
45 45 {:noreply, stream_delete(socket, :plani, plani)}
46 46 end
47 +
48 + # Ids from the URL or the client are looked up among the user's own.
49 + defp owner(socket), do: {:user, socket.assigns.current_user.id}
47 50 end
modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_plani_live/show.ex
+16 −4
@@ -25,9 +25,9 @@ defmodule RoomSanctumWeb.PlaniLive.Show do
25 25 {:noreply,
26 26 socket
27 27 |> assign(:page_title, "Plani")
28 − |> assign(:plani, Configuration.get_plani!(id))
29 − |> assign(:sources, Configuration.list_cfg_sources({:user, socket.assigns.current_user.id}))
30 − |> assign(:pythiae, Configuration.list_pythiae(:plani, id))
28 + |> assign(:plani, Configuration.get_plani!(owner(socket), id))
29 + |> assign(:sources, Configuration.list_cfg_sources({:user, socket.assigns.current_user.id}))
30 + |> assign(:pythiae, own_pythiae(socket, id))
31 31 |> look()}
32 32 end
33 33
@@ -113,7 +113,8 @@ defmodule RoomSanctumWeb.PlaniLive.Show do
113 113 curvature to show, and the longitude scale is taken at the ring's own
114 114 latitude so it does not go oval further north.
115 115 """
116 − def radius_ring(%{anchor: %Geo.Point{coordinates: {lon, lat}}}, radius_m) when is_integer(radius_m) do
116 + def radius_ring(%{anchor: %Geo.Point{coordinates: {lon, lat}}}, radius_m)
117 + when is_integer(radius_m) do
117 118 d_lat = radius_m / 111_320
118 119 d_lon = radius_m / (111_320 * max(:math.cos(lat * :math.pi() / 180), 0.01))
119 120
@@ -270,4 +271,15 @@ defmodule RoomSanctumWeb.PlaniLive.Show do
270 271 nil -> nil
271 272 end
272 273 end
274 +
275 + # The pythiae driven by this plani, of the user's own: another user's screen
276 + # pointed at it is none of this page's business.
277 + defp own_pythiae(socket, id) do
278 + :plani
279 + |> Configuration.list_pythiae(id)
280 + |> Enum.filter(&(&1.user_id == socket.assigns.current_user.id))
281 + end
282 +
283 + # The plani comes from the URL, so it is looked up among the user's own.
284 + defp owner(socket), do: {:user, socket.assigns.current_user.id}
273 285 end
modified apps/room_sanctum/test/room_sanctum_web/live/ownership_test.exs
+41 −0
@@ -387,4 +387,45 @@ defmodule RoomSanctumWeb.OwnershipTest do
387 387 assert Repo.reload!(screen).curr_vision == second.id
388 388 end
389 389 end
390 +
391 + defp plani_for(user) do
392 + {:ok, plani} =
393 + Configuration.create_plani(%{
394 + name: "Pocket",
395 + user_id: user.id,
396 + home_foci_id: foci_for(user).id,
397 + sources: []
398 + })
399 +
400 + plani
401 + end
402 +
403 + describe "the plani pages" do
404 + setup :users_rabbit!
405 +
406 + test "will not show another user's plani", ctx do
407 + theirs = plani_for(ctx.them)
408 +
409 + assert_raise Ecto.NoResultsError, fn ->
410 + live(ctx.conn, Routes.plani_show_path(ctx.conn, :show, theirs))
411 + end
412 + end
413 +
414 + test "will not open another user's plani for editing", ctx do
415 + theirs = plani_for(ctx.them)
416 +
417 + assert_raise Ecto.NoResultsError, fn ->
418 + live(ctx.conn, Routes.plani_index_path(ctx.conn, :edit, theirs))
419 + end
420 + end
421 +
422 + test "will not delete another user's plani", ctx do
423 + theirs = plani_for(ctx.them)
424 + {:ok, live, _html} = live(ctx.conn, Routes.plani_index_path(ctx.conn, :index))
425 +
426 + forge(live, "delete", %{"id" => to_string(theirs.id)})
427 +
428 + assert Repo.reload(theirs)
429 + end
430 + end
390 431 end

Parents: 099a0e1