first(); if (! $app) { return response()->json(['pages' => [], 'users' => [], 'groups' => []]); } $pages = NavItem::where('app_id', $app->id) ->where('is_folder', false) ->whereNotNull('route_name') ->orderBy('sort_order') ->get(['id', 'label', 'route_name']); $grants = UnifiPageGrant::whereIn('nav_item_id', $pages->pluck('id')) ->get() ->groupBy('nav_item_id'); // Only return users that ALREADY have grants. The full users list // can be enormous (thousands of rows); the operator adds more via // the searchUsers endpoint as needed. $grantedUserIds = $grants->flatten(1)->where('grantee_type', 'user')->pluck('grantee_id')->unique(); $users = User::whereIn('id', $grantedUserIds)->orderBy('name')->get(['id', 'name', 'email']); // Groups: always include super-admin groups (locked-on across all // pages) plus any group with at least one grant. Other groups are // added via searchGroups. $grantedGroupIds = $grants->flatten(1)->where('grantee_type', 'group')->pluck('grantee_id')->unique(); $groups = Group::where(function ($q) use ($grantedGroupIds) { $q->where('is_super', true) ->orWhereIn('id', $grantedGroupIds); })->orderBy('name')->get(['id', 'name', 'is_super']); return response()->json([ 'pages' => $pages->map(fn ($p) => [ 'id' => $p->id, 'label' => $p->label, 'route_name' => $p->route_name, 'user_ids' => $grants->get($p->id, collect())->where('grantee_type', 'user')->pluck('grantee_id')->all(), 'group_ids' => $grants->get($p->id, collect())->where('grantee_type', 'group')->pluck('grantee_id')->all(), ])->values(), 'users' => $users, 'groups' => $groups, ]); } /** * Typeahead-style search for users to add to the access matrix. * Returns up to 20 matches against name or email. Empty query returns * an empty array — caller must enter at least 2 chars. */ public function searchUsers(Request $request) { $q = trim((string) $request->query('q', '')); if (strlen($q) < 2) { return response()->json(['users' => []]); } $users = User::where(function ($w) use ($q) { $w->where('name', 'like', '%' . $q . '%') ->orWhere('email', 'like', '%' . $q . '%'); }) ->orderBy('name') ->limit(20) ->get(['id', 'name', 'email']); return response()->json(['users' => $users]); } /** * Typeahead-style search for groups to add to the access matrix. * Excludes super-admin groups (they're already in the matrix and * locked-on across every page). */ public function searchGroups(Request $request) { $q = trim((string) $request->query('q', '')); if (strlen($q) < 2) { return response()->json(['groups' => []]); } $groups = Group::where('name', 'like', '%' . $q . '%') ->where(function ($w) { $w->where('is_super', false)->orWhereNull('is_super'); }) ->orderBy('name') ->limit(20) ->get(['id', 'name', 'is_super']); return response()->json(['groups' => $groups]); } public function update(Request $request, NavItem $navItem) { $app = DashboardApp::where('slug', 'unifi')->first(); if (! $app || $navItem->app_id !== $app->id) { return response()->json(['error' => 'Not a unifi page.'], 422); } $data = $request->validate([ 'user_ids' => 'present|array', 'user_ids.*' => 'integer|exists:users,id', 'group_ids' => 'present|array', 'group_ids.*' => 'integer|exists:groups,id', ]); $grantedBy = $request->user()?->id; DB::transaction(function () use ($navItem, $data, $grantedBy) { UnifiPageGrant::where('nav_item_id', $navItem->id)->delete(); $rows = []; $now = now(); foreach ($data['user_ids'] as $uid) { $rows[] = ['nav_item_id' => $navItem->id, 'grantee_type' => 'user', 'grantee_id' => $uid, 'granted_by_user_id' => $grantedBy, 'created_at' => $now, 'updated_at' => $now]; } foreach ($data['group_ids'] as $gid) { $rows[] = ['nav_item_id' => $navItem->id, 'grantee_type' => 'group', 'grantee_id' => $gid, 'granted_by_user_id' => $grantedBy, 'created_at' => $now, 'updated_at' => $now]; } if ($rows) UnifiPageGrant::insert($rows); }); return response()->json(['ok' => true]); } }