Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
76.47% covered (warning)
76.47%
39 / 51
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
StaffService
76.47% covered (warning)
76.47%
39 / 51
20.00% covered (danger)
20.00%
1 / 5
16.55
0.00% covered (danger)
0.00%
0 / 1
 list
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
1.01
 create
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 update
80.00% covered (warning)
80.00%
16 / 20
0.00% covered (danger)
0.00%
0 / 1
6.29
 delete
28.57% covered (danger)
28.57%
2 / 7
0.00% covered (danger)
0.00%
0 / 1
14.11
 ownerCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Services\Permission;
4
5use App\Models\User;
6use Illuminate\Contracts\Pagination\LengthAwarePaginator;
7use Illuminate\Support\Facades\Hash;
8use Illuminate\Validation\ValidationException;
9
10/**
11 * Add / edit / remove the people who can sign in to the admin panel.
12 * A staff member is just a `users` row with role = "1" (admin) plus one
13 * assigned RBAC role. Deliberately tiny — see PROJECT_BRAIN.md Phase 3.
14 */
15class StaffService
16{
17    /** Admin logins, newest first, with their assigned role loaded. */
18    public function list(?string $search = null): LengthAwarePaginator
19    {
20        return User::query()
21            ->where('role', '1')
22            ->with('roles:id,name')
23            ->when($search, fn ($q) => $q->where(function ($q) use ($search) {
24                $q->where('name', 'like', "%{$search}%")
25                    ->orWhere('email', 'like', "%{$search}%");
26            }))
27            ->orderBy('id')
28            ->paginate(20)
29            ->withQueryString();
30    }
31
32    public function create(array $data): User
33    {
34        $user = User::query()->create([
35            'name' => $data['name'],
36            'email' => $data['email'],
37            'password' => Hash::make($data['password']),
38            'role' => '1',
39            'status' => $data['status'] ?? 1,
40        ]);
41
42        $user->syncRoles([$data['role']]);
43
44        activity('staff')
45            ->performedOn($user)
46            ->withProperties(['attributes' => ['role' => $data['role']]])
47            ->log("Assigned role “{$data['role']}” to {$user->name}");
48
49        return $user;
50    }
51
52    public function update(User $user, array $data): User
53    {
54        $user->fill([
55            'name' => $data['name'],
56            'email' => $data['email'],
57            'status' => $data['status'] ?? $user->status,
58        ]);
59
60        if (! empty($data['password'])) {
61            $user->password = Hash::make($data['password']);
62        }
63
64        $user->save();
65
66        // Never let the last Owner be demoted — that would lock everyone out.
67        if ($user->hasRole('Owner') && $data['role'] !== 'Owner' && $this->ownerCount() <= 1) {
68            throw ValidationException::withMessages([
69                'role' => 'This is the only Owner. Make someone else an Owner first.',
70            ]);
71        }
72
73        if (! $user->hasRole($data['role'])) {
74            $from = $user->roles->first()?->name ?? 'none';
75            $user->syncRoles([$data['role']]);
76
77            activity('staff')
78                ->performedOn($user)
79                ->withProperties(['old' => ['role' => $from], 'attributes' => ['role' => $data['role']]])
80                ->log("Changed {$user->name}'s role from “{$from}” to “{$data['role']}");
81        }
82
83        return $user;
84    }
85
86    public function delete(User $user): void
87    {
88        if ((int) $user->id === 1) {
89            throw ValidationException::withMessages(['staff' => 'The main account cannot be removed.']);
90        }
91
92        if ((int) $user->id === (int) auth()->id()) {
93            throw ValidationException::withMessages(['staff' => 'You cannot remove your own account.']);
94        }
95
96        if ($user->hasRole('Owner') && $this->ownerCount() <= 1) {
97            throw ValidationException::withMessages(['staff' => 'This is the only Owner. Assign another Owner first.']);
98        }
99
100        $user->delete();
101    }
102
103    private function ownerCount(): int
104    {
105        return User::role('Owner')->count();
106    }
107}