feat(rover): admins can edit/delete any rover location
Rover.update_location/3 and delete_location/2 now resolve any record when the actor is_admin. The index + show LiveViews surface Edit/Delete on every row for admin users, matching the existing owner UX.
This commit is contained in:
parent
558980a191
commit
d052dcaa91
5 changed files with 109 additions and 22 deletions
|
|
@ -168,6 +168,13 @@ defmodule Microwaveprop.Rover do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp fetch_owned_location(%User{is_admin: true}, id) do
|
||||||
|
case Repo.get(Location, id) do
|
||||||
|
%Location{} = loc -> {:ok, loc}
|
||||||
|
_ -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp fetch_owned_location(%User{id: user_id}, id) do
|
defp fetch_owned_location(%User{id: user_id}, id) do
|
||||||
case Repo.get(Location, id) do
|
case Repo.get(Location, id) do
|
||||||
%Location{user_id: ^user_id} = loc -> {:ok, loc}
|
%Location{user_id: ^user_id} = loc -> {:ok, loc}
|
||||||
|
|
|
||||||
|
|
@ -83,20 +83,19 @@ defmodule MicrowavepropWeb.RoverLocationsLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("edit", %{"id" => id}, socket) do
|
def handle_event("edit", %{"id" => id}, socket) do
|
||||||
case current_user(socket) do
|
user = current_user(socket)
|
||||||
%User{id: user_id} ->
|
|
||||||
case Repo.get(Location, id) do
|
|
||||||
%Location{user_id: ^user_id} = loc ->
|
|
||||||
cs = Location.changeset(loc, %{})
|
|
||||||
grid = Maidenhead.from_latlon(loc.lat, loc.lon, 6)
|
|
||||||
{:noreply, assign(socket, form: to_form(cs), editing_id: id, grid_input: grid)}
|
|
||||||
|
|
||||||
_ ->
|
cond do
|
||||||
{:noreply, put_flash(socket, :error, "You can only edit your own locations.")}
|
is_nil(user) ->
|
||||||
end
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:noreply, put_flash(socket, :error, "Sign in to edit a location.")}
|
{:noreply, put_flash(socket, :error, "Sign in to edit a location.")}
|
||||||
|
|
||||||
|
loc = editable_location(user, id) ->
|
||||||
|
cs = Location.changeset(loc, %{})
|
||||||
|
grid = Maidenhead.from_latlon(loc.lat, loc.lon, 6)
|
||||||
|
{:noreply, assign(socket, form: to_form(cs), editing_id: id, grid_input: grid)}
|
||||||
|
|
||||||
|
true ->
|
||||||
|
{:noreply, put_flash(socket, :error, "You can only edit your own locations.")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -184,6 +183,17 @@ defmodule MicrowavepropWeb.RoverLocationsLive do
|
||||||
|
|
||||||
defp location_for_edit(_), do: %Location{}
|
defp location_for_edit(_), do: %Location{}
|
||||||
|
|
||||||
|
defp editable_location(%User{is_admin: true}, id) do
|
||||||
|
Repo.get(Location, id)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp editable_location(%User{id: user_id}, id) do
|
||||||
|
case Repo.get(Location, id) do
|
||||||
|
%Location{user_id: ^user_id} = loc -> loc
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp blank_form(_user) do
|
defp blank_form(_user) do
|
||||||
%Location{} |> Location.changeset(%{}) |> to_form()
|
%Location{} |> Location.changeset(%{}) |> to_form()
|
||||||
end
|
end
|
||||||
|
|
@ -254,16 +264,15 @@ defmodule MicrowavepropWeb.RoverLocationsLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp owner?(_scope, %{user_id: nil}), do: false
|
defp can_modify?(scope, %{user_id: uid}) do
|
||||||
|
|
||||||
defp owner?(scope, %{user_id: uid}) do
|
|
||||||
case scope_user(%{current_scope: scope}) do
|
case scope_user(%{current_scope: scope}) do
|
||||||
%User{id: ^uid} -> true
|
%User{is_admin: true} -> true
|
||||||
|
%User{id: ^uid} when not is_nil(uid) -> true
|
||||||
_ -> false
|
_ -> false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp owner?(_, _), do: false
|
defp can_modify?(_, _), do: false
|
||||||
|
|
||||||
defp actions_for(scope) do
|
defp actions_for(scope) do
|
||||||
[
|
[
|
||||||
|
|
@ -274,10 +283,10 @@ defmodule MicrowavepropWeb.RoverLocationsLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp row_actions(record, scope) do
|
defp row_actions(record, scope) do
|
||||||
assigns = %{record: record, owner?: owner?(scope, record)}
|
assigns = %{record: record, can_modify?: can_modify?(scope, record)}
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div :if={@owner?} class="flex gap-2">
|
<div :if={@can_modify?} class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="edit"
|
phx-click="edit"
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,10 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp owner?(scope, %Location{user_id: user_id}) do
|
defp can_modify?(scope, %Location{user_id: user_id}) do
|
||||||
case current_user(scope) do
|
case current_user(scope) do
|
||||||
%User{id: ^user_id} -> true
|
%User{is_admin: true} -> true
|
||||||
|
%User{id: ^user_id} when not is_nil(user_id) -> true
|
||||||
_ -> false
|
_ -> false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -91,7 +92,7 @@ defmodule MicrowavepropWeb.RoverLocationsLive.Show do
|
||||||
<.icon name="hero-arrow-left" class="w-4 h-4" /> Back
|
<.icon name="hero-arrow-left" class="w-4 h-4" /> Back
|
||||||
</.link>
|
</.link>
|
||||||
<button
|
<button
|
||||||
:if={owner?(@current_scope, @location)}
|
:if={can_modify?(@current_scope, @location)}
|
||||||
type="button"
|
type="button"
|
||||||
phx-click="delete"
|
phx-click="delete"
|
||||||
data-confirm="Delete this location?"
|
data-confirm="Delete this location?"
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,17 @@ defmodule MicrowavepropWeb.RoverLocationsLive.ShowTest do
|
||||||
assert html =~ "Delete"
|
assert html =~ "Delete"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "admin sees Delete on someone else's location", %{conn: conn} do
|
||||||
|
owner = Microwaveprop.AccountsFixtures.user_fixture()
|
||||||
|
admin = Microwaveprop.AccountsFixtures.user_fixture()
|
||||||
|
{:ok, admin} = Microwaveprop.Accounts.admin_update_user(admin, %{is_admin: true})
|
||||||
|
{:ok, loc} = Rover.create_location(owner, %{lat: 32.5, lon: -97.5, status: :ideal})
|
||||||
|
|
||||||
|
conn = log_in_user(conn, admin)
|
||||||
|
{:ok, _lv, html} = live(conn, ~p"/rover-locations/#{loc.id}")
|
||||||
|
assert html =~ "Delete"
|
||||||
|
end
|
||||||
|
|
||||||
test "owner can delete from the show page and is redirected", %{conn: conn} do
|
test "owner can delete from the show page and is redirected", %{conn: conn} do
|
||||||
user = Microwaveprop.AccountsFixtures.user_fixture()
|
user = Microwaveprop.AccountsFixtures.user_fixture()
|
||||||
{:ok, loc} = Rover.create_location(user, %{lat: 32.5, lon: -97.5, status: :ideal})
|
{:ok, loc} = Rover.create_location(user, %{lat: 32.5, lon: -97.5, status: :ideal})
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ defmodule MicrowavepropWeb.RoverLocationsLiveTest do
|
||||||
import Phoenix.LiveViewTest
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
alias Microwaveprop.Rover
|
alias Microwaveprop.Rover
|
||||||
|
alias Microwaveprop.Rover.Location
|
||||||
|
|
||||||
describe "anonymous visitor" do
|
describe "anonymous visitor" do
|
||||||
test "can view the locations page", %{conn: conn} do
|
test "can view the locations page", %{conn: conn} do
|
||||||
|
|
@ -148,4 +149,62 @@ defmodule MicrowavepropWeb.RoverLocationsLiveTest do
|
||||||
refute has_element?(lv, "button[phx-click=delete][phx-value-id='#{theirs.id}']", "Delete")
|
refute has_element?(lv, "button[phx-click=delete][phx-value-id='#{theirs.id}']", "Delete")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "admin" do
|
||||||
|
test "sees edit/delete on every row and can delete a stranger's location", %{conn: conn} do
|
||||||
|
admin = Microwaveprop.AccountsFixtures.user_fixture()
|
||||||
|
{:ok, admin} = Microwaveprop.Accounts.admin_update_user(admin, %{is_admin: true})
|
||||||
|
other = Microwaveprop.AccountsFixtures.user_fixture()
|
||||||
|
|
||||||
|
{:ok, theirs} = Rover.create_location(other, %{lat: 33.0, lon: -98.0, status: :ideal})
|
||||||
|
|
||||||
|
conn = log_in_user(conn, admin)
|
||||||
|
{:ok, lv, _html} = live(conn, ~p"/rover-locations")
|
||||||
|
|
||||||
|
assert has_element?(lv, "button[phx-click=edit][phx-value-id='#{theirs.id}']", "Edit")
|
||||||
|
assert has_element?(lv, "button[phx-click=delete][phx-value-id='#{theirs.id}']", "Delete")
|
||||||
|
|
||||||
|
lv
|
||||||
|
|> element("button[phx-click=delete][phx-value-id='#{theirs.id}']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
refute Microwaveprop.Repo.get(Location, theirs.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can edit a stranger's location", %{conn: conn} do
|
||||||
|
admin = Microwaveprop.AccountsFixtures.user_fixture()
|
||||||
|
{:ok, admin} = Microwaveprop.Accounts.admin_update_user(admin, %{is_admin: true})
|
||||||
|
other = Microwaveprop.AccountsFixtures.user_fixture()
|
||||||
|
|
||||||
|
{:ok, theirs} =
|
||||||
|
Rover.create_location(other, %{
|
||||||
|
lat: 33.0,
|
||||||
|
lon: -98.0,
|
||||||
|
status: :ideal,
|
||||||
|
notes: "old"
|
||||||
|
})
|
||||||
|
|
||||||
|
conn = log_in_user(conn, admin)
|
||||||
|
{:ok, lv, _html} = live(conn, ~p"/rover-locations")
|
||||||
|
|
||||||
|
lv
|
||||||
|
|> element("button[phx-click=edit][phx-value-id='#{theirs.id}']")
|
||||||
|
|> render_click()
|
||||||
|
|
||||||
|
lv
|
||||||
|
|> form("#location-form",
|
||||||
|
location: %{
|
||||||
|
lat: "33.0",
|
||||||
|
lon: "-98.0",
|
||||||
|
status: "off_limits",
|
||||||
|
notes: "admin override"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
updated = Microwaveprop.Repo.get(Location, theirs.id)
|
||||||
|
assert updated.notes == "admin override"
|
||||||
|
assert updated.status == :off_limits
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue