neiam
/apollos_crib
It's Apollo's Crib
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions failure · Badges · Packages
⭐
Log in to mark this repository.
Visions: the vision pages only reach the user's own visions and pythiae
9eda73e · Gabe Morell · 2026-11-04 22:01
Message
{commit_body(@commit)}
Files changed
modified
apps/room_sanctum/lib/room_sanctum/configuration.ex
+10
−0
@@ -393,6 +393,9 @@ defmodule RoomSanctum.Configuration do
| 393 | 393 | """ |
| 394 | 394 | def get_vision!(id), do: Repo.get!(Vision, id) |
| 395 | 395 | |
| 396 | + # Someone else's vision raises the not-found a missing one does. | |
| 397 | + def get_vision!({:user, uid}, id), do: Repo.get_by!(Vision, id: id, user_id: uid) | |
| 398 | + | |
| 396 | 399 | def get_vision(id), do: Repo.get(Vision, id) |
| 397 | 400 | |
| 398 | 401 | def get_vision({:user, uid}, id), do: Repo.get_by(Vision, id: id, user_id: uid) |
@@ -680,6 +683,9 @@ defmodule RoomSanctum.Configuration do
| 680 | 683 | """ |
| 681 | 684 | def get_pythiae!(id), do: Repo.get!(Pythiae, id) |
| 682 | 685 | |
| 686 | + # Someone else's pythiae raises the not-found a missing one does. | |
| 687 | + def get_pythiae!({:user, uid}, id), do: Repo.get_by!(Pythiae, id: id, user_id: uid) | |
| 688 | + | |
| 683 | 689 | alias RoomSanctum.Configuration.Plani |
| 684 | 690 | |
| 685 | 691 | @doc "Every Plani, for the supervisor that keeps a worker per one." |
@@ -748,6 +754,10 @@ defmodule RoomSanctum.Configuration do
| 748 | 754 | from(p in Pythiae, where: ^id in p.visions) |> Repo.all() |
| 749 | 755 | end |
| 750 | 756 | |
| 757 | + def get_pythiae(:vision, id, user_id) do | |
| 758 | + from(p in Pythiae, where: p.user_id == ^user_id and ^id in p.visions) |> Repo.all() | |
| 759 | + end | |
| 760 | + | |
| 751 | 761 | @doc """ |
| 752 | 762 | The pythiae a vision is *not* on -- the ones it could be added to. |
| 753 | 763 |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_vision_live/index.ex
+5
−2
@@ -20,7 +20,7 @@ defmodule RoomSanctumWeb.VisionLive.Index do
| 20 | 20 | defp apply_action(socket, :edit, %{"id" => id}) do |
| 21 | 21 | socket |
| 22 | 22 | |> assign(:page_title, "Modify Vision") |
| 23 | − |> assign(:vision, Configuration.get_vision!(id)) | |
| 23 | + |> assign(:vision, Configuration.get_vision!(owner(socket), id)) | |
| 24 | 24 | end |
| 25 | 25 | |
| 26 | 26 | defp apply_action(socket, :new, _params) do |
@@ -42,7 +42,7 @@ defmodule RoomSanctumWeb.VisionLive.Index do
| 42 | 42 | |
| 43 | 43 | @impl true |
| 44 | 44 | def handle_event("delete", %{"id" => id}, socket) do |
| 45 | − vision = Configuration.get_vision!(id) | |
| 45 | + vision = Configuration.get_vision!(owner(socket), id) | |
| 46 | 46 | {:ok, _} = Configuration.delete_vision(vision) |
| 47 | 47 | |
| 48 | 48 | {:noreply, stream_delete(socket, :visions, vision)} |
@@ -55,4 +55,7 @@ defmodule RoomSanctumWeb.VisionLive.Index do
| 55 | 55 | defp list_visions(uid) do |
| 56 | 56 | Configuration.list_visions({:user, uid}) |
| 57 | 57 | end |
| 58 | + | |
| 59 | + # Ids from the URL or the client are looked up among the user's own. | |
| 60 | + defp owner(socket), do: {:user, socket.assigns.current_user.id} | |
| 58 | 61 | end |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_vision_live/show.ex
+12
−5
@@ -24,7 +24,10 @@ defmodule RoomSanctumWeb.VisionLive.Show do
| 24 | 24 | vision_id = String.to_integer(to_string(socket.assigns.vision_id)) |
| 25 | 25 | |
| 26 | 26 | socket |
| 27 | − |> assign(:pythiae, Configuration.get_pythiae(:vision, vision_id)) | |
| 27 | + |> assign( | |
| 28 | + :pythiae, | |
| 29 | + Configuration.get_pythiae(:vision, vision_id, socket.assigns.current_user.id) | |
| 30 | + ) | |
| 28 | 31 | |> assign( |
| 29 | 32 | :avail_pythiae, |
| 30 | 33 | Configuration.get_pythiae_nv(:vision, vision_id, socket.assigns.current_user.id) |
@@ -36,7 +39,7 @@ defmodule RoomSanctumWeb.VisionLive.Show do
| 36 | 39 | {:noreply, |
| 37 | 40 | socket |
| 38 | 41 | |> assign(:page_title, page_title(socket.assigns.live_action)) |
| 39 | − |> assign(:vision, Configuration.get_vision!(id)) | |
| 42 | + |> assign(:vision, Configuration.get_vision!(owner(socket), id)) | |
| 40 | 43 | |> assign(:vision_id, id) |
| 41 | 44 | |> assign(:preview, []) |
| 42 | 45 | |> assign(:queries, []) |
@@ -122,8 +125,8 @@ defmodule RoomSanctumWeb.VisionLive.Show do
| 122 | 125 | end |
| 123 | 126 | |
| 124 | 127 | def handle_event("add-to-pythiae", %{"pythiae" => id}, socket) do |
| 125 | − id | |
| 126 | − |> Configuration.get_pythiae!() | |
| 128 | + owner(socket) | |
| 129 | + |> Configuration.get_pythiae!(id) | |
| 127 | 130 | |> show_vision(socket) |
| 128 | 131 | end |
| 129 | 132 |
@@ -158,7 +161,7 @@ defmodule RoomSanctumWeb.VisionLive.Show do
| 158 | 161 | end |
| 159 | 162 | |
| 160 | 163 | def handle_event("remove-from-pythiae", %{"pythiae" => id}, socket) do |
| 161 | − pythiae = Configuration.get_pythiae!(id) | |
| 164 | + pythiae = Configuration.get_pythiae!(owner(socket), id) | |
| 162 | 165 | vision_id = String.to_integer(to_string(socket.assigns.vision_id)) |
| 163 | 166 | visions = (pythiae.visions || []) -- [vision_id] |
| 164 | 167 |
@@ -285,4 +288,8 @@ defmodule RoomSanctumWeb.VisionLive.Show do
| 285 | 288 | def preview(condensed, {id, type}) do |
| 286 | 289 | %{data: condensed, id: id, type: type} |
| 287 | 290 | end |
| 291 | + | |
| 292 | + # The vision, and any pythiae an event names, are looked up among the | |
| 293 | + # user's own: ids from the URL or the client are not trusted. | |
| 294 | + defp owner(socket), do: {:user, socket.assigns.current_user.id} | |
| 288 | 295 | end |
modified
apps/room_sanctum/test/room_sanctum_web/live/ownership_test.exs
+72
−0
@@ -79,4 +79,76 @@ defmodule RoomSanctumWeb.OwnershipTest do
| 79 | 79 | assert Repo.reload(theirs) |
| 80 | 80 | end |
| 81 | 81 | end |
| 82 | + | |
| 83 | + defp vision_for(user, name) do | |
| 84 | + {:ok, vision} = Configuration.create_vision(%{name: name, user_id: user.id}) | |
| 85 | + vision | |
| 86 | + end | |
| 87 | + | |
| 88 | + defp pythiae_for(user, visions) do | |
| 89 | + {:ok, pythiae} = | |
| 90 | + Configuration.create_pythiae(%{ | |
| 91 | + name: "screen-#{System.unique_integer([:positive])}", | |
| 92 | + user_id: user.id, | |
| 93 | + ankyra: [], | |
| 94 | + visions: visions, | |
| 95 | + curr_vision: List.first(visions) | |
| 96 | + }) | |
| 97 | + | |
| 98 | + pythiae | |
| 99 | + end | |
| 100 | + | |
| 101 | + describe "the vision pages" do | |
| 102 | + test "will not show another user's vision", ctx do | |
| 103 | + theirs = vision_for(ctx.them, "Their dash") | |
| 104 | + | |
| 105 | + assert_raise Ecto.NoResultsError, fn -> | |
| 106 | + live(ctx.conn, Routes.vision_show_path(ctx.conn, :show, theirs)) | |
| 107 | + end | |
| 108 | + end | |
| 109 | + | |
| 110 | + test "will not open another user's vision for editing", ctx do | |
| 111 | + theirs = vision_for(ctx.them, "Their dash") | |
| 112 | + | |
| 113 | + assert_raise Ecto.NoResultsError, fn -> | |
| 114 | + live(ctx.conn, Routes.vision_index_path(ctx.conn, :edit, theirs)) | |
| 115 | + end | |
| 116 | + end | |
| 117 | + | |
| 118 | + test "will not delete another user's vision", ctx do | |
| 119 | + theirs = vision_for(ctx.them, "Their dash") | |
| 120 | + {:ok, live, _html} = live(ctx.conn, Routes.vision_index_path(ctx.conn, :index)) | |
| 121 | + | |
| 122 | + forge(live, "delete", %{"id" => to_string(theirs.id)}) | |
| 123 | + | |
| 124 | + assert Repo.reload(theirs) | |
| 125 | + end | |
| 126 | + | |
| 127 | + test "will not put the vision on another user's pythiae", ctx do | |
| 128 | + mine = vision_for(ctx.me, "My dash") | |
| 129 | + their_screen = pythiae_for(ctx.them, []) | |
| 130 | + {:ok, live, _html} = live(ctx.conn, Routes.vision_show_path(ctx.conn, :show, mine)) | |
| 131 | + | |
| 132 | + forge(live, "add-to-pythiae", %{"pythiae" => to_string(their_screen.id)}) | |
| 133 | + | |
| 134 | + assert Repo.reload!(their_screen).visions == [] | |
| 135 | + end | |
| 136 | + | |
| 137 | + test "neither lists nor edits another user's pythiae that holds the vision", ctx do | |
| 138 | + mine = vision_for(ctx.me, "My dash") | |
| 139 | + their_screen = pythiae_for(ctx.them, [mine.id]) | |
| 140 | + {:ok, live, _html} = live(ctx.conn, Routes.vision_show_path(ctx.conn, :show, mine)) | |
| 141 | + send(live.pid, :update_sec) | |
| 142 | + render_click(live, "toggle-pythiae-sel", %{}) | |
| 143 | + | |
| 144 | + refute has_element?( | |
| 145 | + live, | |
| 146 | + ~s([phx-click="remove-from-pythiae"][phx-value-pythiae="#{their_screen.id}"]) | |
| 147 | + ) | |
| 148 | + | |
| 149 | + forge(live, "remove-from-pythiae", %{"pythiae" => to_string(their_screen.id)}) | |
| 150 | + | |
| 151 | + assert Repo.reload!(their_screen).visions == [mine.id] | |
| 152 | + end | |
| 153 | + end | |
| 82 | 154 | end |
Parents: c056659