mergeConfigFrom(__DIR__ . '/../config/unifi.php', 'unifi'); $this->app->singleton(Services\UnifiApiClient::class, function ($app) { return new Services\UnifiApiClient(); }); } public function boot(): void { $this->loadRoutesFrom(__DIR__ . '/routes/unifi.php'); $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); // "/" home card: guest Wi-Fi network + current rotating password + // QR (re-renders when the password changes). Self-hides until enabled // and a password has been rotated. // // The ONE card in the platform that earned a NEW permission key rather // than reusing one. It cannot be universal — the payload is a live // credential, and publishing it to every student and parent is the exact // hole GuestCardController::data was written to close. And no existing // unifi key fits: unifi.stats is the network dashboards, unifi.settings is // the controller connection, and handing the front desk either one to let // them read a guest password would widen access, not reuse it. So the // granularity goes in the catalogue (same move as the papercut/inventory // split), and the key is grantable to non-unifi staff — which is the whole // point of a home-page card. // // Declared in composer.json extra.dashboard.permissions, so the catalogue // row appears when unifi is installed/updated — NOT seeded to any group. try { app(\App\Support\DashboardCardRegistry::class)->register( 'unifi.guest_wifi', 'Guest Wi-Fi', 'UnifiGuestWifi', fn (\App\Models\User $user) => \Dashboard\Unifi\Http\Controllers\GuestCardController::resolve($user), 'The guest network name, current password, and a join QR code — updates automatically when the password rotates.', permission: 'unifi.guest_wifi', ); } catch (\Throwable) { // Older shell without the card registry — skip. } // Tell the shell's nav sidebar which unifi nav items the user // can see: pages whose required_permission the user holds through // groups, plus pages granted via the Settings → Access tab. The // shell also folds these grants back into User::can()/allPermissions // so the route-level permission middleware passes for grantees. if ($this->app->runningInConsole()) { $this->commands([ Console\CheckWebhooks::class, Console\CaptureSnapshots::class, Console\CleanupSnapshots::class, Console\RebootAllAps::class, Console\RotatePasswords::class, Console\SyncPpskSchedules::class, ]); $this->publishes([ __DIR__ . '/../config/unifi.php' => config_path('unifi.php'), ], 'unifi-config'); } } /** * Group + direct permissions only. Deliberately NOT allPermissions(): * the shell folds page-grant permissions back into allPermissions/can, * so using it here would turn a single granted page into every page * carrying the same permission. */ public static function realPermissionKeysFor(\App\Models\User $user): \Illuminate\Support\Collection { if ($user->is_super_admin) { return \App\Models\Permission::pluck('key'); } $direct = $user->directPermissions()->get(); return \App\Models\Permission::whereHas('groups', fn ($q) => $q->whereIn('group_id', $user->groups()->pluck('groups.id'))) ->pluck('key') ->merge($direct->where('granted', true)->pluck('permission_key')) ->diff($direct->where('granted', false)->pluck('permission_key')) ->unique() ->values(); } }