Add statistics route, permissions, and sidebar entry

- /statistics route and PagePaths.statistics
- Permission sets: viewer and admin can access /statistics
- Sidebar link with can_access_page check
- Plug and sidebar tests updated

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Moritz 2026-02-10 22:31:35 +01:00
parent 7b83011dcb
commit cf79bbd1c4
7 changed files with 61 additions and 6 deletions

View file

@ -26,6 +26,7 @@ This document lists all protected routes, which permission set may access them,
| `/groups/new` | ✗ | ✗ | ✗ | ✓ | | `/groups/new` | ✗ | ✗ | ✗ | ✓ |
| `/groups/:slug` | ✗ | ✓ | ✓ | ✓ | | `/groups/:slug` | ✗ | ✓ | ✓ | ✓ |
| `/groups/:slug/edit` | ✗ | ✗ | ✗ | ✓ | | `/groups/:slug/edit` | ✗ | ✗ | ✗ | ✓ |
| `/statistics` | ✗ | ✓ | ✓ | ✓ |
| `/admin/roles` | ✗ | ✗ | ✗ | ✓ | | `/admin/roles` | ✗ | ✗ | ✗ | ✓ |
| `/admin/roles/new` | ✗ | ✗ | ✗ | ✓ | | `/admin/roles/new` | ✗ | ✗ | ✗ | ✓ |
| `/admin/roles/:id` | ✗ | ✗ | ✗ | ✓ | | `/admin/roles/:id` | ✗ | ✗ | ✗ | ✓ |

View file

@ -178,7 +178,9 @@ defmodule Mv.Authorization.PermissionSets do
# Groups overview # Groups overview
"/groups", "/groups",
# Group detail # Group detail
"/groups/:slug" "/groups/:slug",
# Statistics
"/statistics"
] ]
} }
end end
@ -243,7 +245,9 @@ defmodule Mv.Authorization.PermissionSets do
# Group detail # Group detail
"/groups/:slug", "/groups/:slug",
# Edit group # Edit group
"/groups/:slug/edit" "/groups/:slug/edit",
# Statistics
"/statistics"
] ]
} }
end end

View file

@ -88,6 +88,14 @@ defmodule MvWeb.Layouts.Sidebar do
/> />
<% end %> <% end %>
<%= if can_access_page?(@current_user, PagePaths.statistics()) do %>
<.menu_item
href={~p"/statistics"}
icon="hero-chart-bar"
label={gettext("Statistics")}
/>
<% end %>
<%= if admin_menu_visible?(@current_user) do %> <%= if admin_menu_visible?(@current_user) do %>
<.menu_group <.menu_group
icon="hero-cog-6-tooth" icon="hero-cog-6-tooth"

View file

@ -9,6 +9,7 @@ defmodule MvWeb.PagePaths do
# Sidebar top-level menu paths # Sidebar top-level menu paths
@members "/members" @members "/members"
@membership_fee_types "/membership_fee_types" @membership_fee_types "/membership_fee_types"
@statistics "/statistics"
# Administration submenu paths (all must match router) # Administration submenu paths (all must match router)
@users "/users" @users "/users"
@ -31,6 +32,9 @@ defmodule MvWeb.PagePaths do
@doc "Path for Membership Fee Types index (sidebar and page permission check)." @doc "Path for Membership Fee Types index (sidebar and page permission check)."
def membership_fee_types, do: @membership_fee_types def membership_fee_types, do: @membership_fee_types
@doc "Path for Statistics page (sidebar and page permission check)."
def statistics, do: @statistics
@doc "Paths for Administration menu; show group if user can access any of these." @doc "Paths for Administration menu; show group if user can access any of these."
def admin_menu_paths, do: @admin_page_paths def admin_menu_paths, do: @admin_page_paths

View file

@ -73,6 +73,9 @@ defmodule MvWeb.Router do
# Membership Fee Types Management # Membership Fee Types Management
live "/membership_fee_types", MembershipFeeTypeLive.Index, :index live "/membership_fee_types", MembershipFeeTypeLive.Index, :index
# Statistics
live "/statistics", StatisticsLive, :index
live "/membership_fee_types/new", MembershipFeeTypeLive.Form, :new live "/membership_fee_types/new", MembershipFeeTypeLive.Form, :new
live "/membership_fee_types/:id/edit", MembershipFeeTypeLive.Form, :edit live "/membership_fee_types/:id/edit", MembershipFeeTypeLive.Form, :edit

View file

@ -25,12 +25,13 @@ defmodule MvWeb.SidebarAuthorizationTest do
end end
describe "sidebar menu with admin user" do describe "sidebar menu with admin user" do
test "shows Members, Fee Types and Administration with all subitems" do test "shows Members, Fee Types, Statistics and Administration with all subitems" do
user = Fixtures.user_with_role_fixture("admin") user = Fixtures.user_with_role_fixture("admin")
html = render_sidebar(sidebar_assigns(user)) html = render_sidebar(sidebar_assigns(user))
assert html =~ ~s(href="/members") assert html =~ ~s(href="/members")
assert html =~ ~s(href="/membership_fee_types") assert html =~ ~s(href="/membership_fee_types")
assert html =~ ~s(href="/statistics")
assert html =~ ~s(data-testid="sidebar-administration") assert html =~ ~s(data-testid="sidebar-administration")
assert html =~ ~s(href="/users") assert html =~ ~s(href="/users")
assert html =~ ~s(href="/groups") assert html =~ ~s(href="/groups")
@ -41,11 +42,12 @@ defmodule MvWeb.SidebarAuthorizationTest do
end end
describe "sidebar menu with read_only user (Vorstand/Buchhaltung)" do describe "sidebar menu with read_only user (Vorstand/Buchhaltung)" do
test "shows Members and Groups (from Administration)" do test "shows Members, Statistics and Groups (from Administration)" do
user = Fixtures.user_with_role_fixture("read_only") user = Fixtures.user_with_role_fixture("read_only")
html = render_sidebar(sidebar_assigns(user)) html = render_sidebar(sidebar_assigns(user))
assert html =~ ~s(href="/members") assert html =~ ~s(href="/members")
assert html =~ ~s(href="/statistics")
assert html =~ ~s(href="/groups") assert html =~ ~s(href="/groups")
end end
@ -61,11 +63,12 @@ defmodule MvWeb.SidebarAuthorizationTest do
end end
describe "sidebar menu with normal_user (Kassenwart)" do describe "sidebar menu with normal_user (Kassenwart)" do
test "shows Members and Groups" do test "shows Members, Statistics and Groups" do
user = Fixtures.user_with_role_fixture("normal_user") user = Fixtures.user_with_role_fixture("normal_user")
html = render_sidebar(sidebar_assigns(user)) html = render_sidebar(sidebar_assigns(user))
assert html =~ ~s(href="/members") assert html =~ ~s(href="/members")
assert html =~ ~s(href="/statistics")
assert html =~ ~s(href="/groups") assert html =~ ~s(href="/groups")
end end
@ -88,10 +91,11 @@ defmodule MvWeb.SidebarAuthorizationTest do
refute html =~ ~s(href="/members") refute html =~ ~s(href="/members")
end end
test "does not show Fee Types or Administration" do test "does not show Statistics, Fee Types or Administration" do
user = Fixtures.user_with_role_fixture("own_data") user = Fixtures.user_with_role_fixture("own_data")
html = render_sidebar(sidebar_assigns(user)) html = render_sidebar(sidebar_assigns(user))
refute html =~ ~s(href="/statistics")
refute html =~ ~s(href="/membership_fee_types") refute html =~ ~s(href="/membership_fee_types")
refute html =~ ~s(href="/users") refute html =~ ~s(href="/users")
refute html =~ ~s(data-testid="sidebar-administration") refute html =~ ~s(data-testid="sidebar-administration")

View file

@ -107,6 +107,37 @@ defmodule MvWeb.Plugs.CheckPagePermissionTest do
end end
end end
describe "statistics route /statistics" do
test "read_only can access /statistics" do
user = Fixtures.user_with_role_fixture("read_only")
conn = conn_with_user("/statistics", user) |> CheckPagePermission.call([])
refute conn.halted
end
test "normal_user can access /statistics" do
user = Fixtures.user_with_role_fixture("normal_user")
conn = conn_with_user("/statistics", user) |> CheckPagePermission.call([])
refute conn.halted
end
test "admin can access /statistics" do
user = Fixtures.user_with_role_fixture("admin")
conn = conn_with_user("/statistics", user) |> CheckPagePermission.call([])
refute conn.halted
end
test "own_data cannot access /statistics" do
user = Fixtures.user_with_role_fixture("own_data")
conn = conn_with_user("/statistics", user) |> CheckPagePermission.call([])
assert conn.halted
assert redirected_to(conn) == "/users/#{user.id}"
end
end
describe "read_only and normal_user denied on admin routes" do describe "read_only and normal_user denied on admin routes" do
test "read_only cannot access /admin/roles" do test "read_only cannot access /admin/roles" do
user = Fixtures.user_with_role_fixture("read_only") user = Fixtures.user_with_role_fixture("read_only")