Skip to content

Commit df6314b

Browse files
authored
Merge pull request #119 from ntoombs19/better-user-management
Better user management
2 parents ceac499 + c1e3ad5 commit df6314b

49 files changed

Lines changed: 1460 additions & 410 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/Data/UserData.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace App\Data;
44

5+
use App\Enums\Permissions;
6+
use App\Enums\Roles;
57
use App\Models\User;
68
use Carbon\Carbon;
79
use Spatie\LaravelData\Data;
@@ -15,9 +17,9 @@ public function __construct(
1517
public string $firstName,
1618
public string $lastName,
1719
public string $email,
18-
/** @var string[] $roles */
20+
/** @var Roles[] $roles */
1921
public array $roles,
20-
/** @var string[] $permissions */
22+
/** @var Permissions[] $permissions */
2123
public array $permissions,
2224
public ?Carbon $createdAt,
2325
public ?Carbon $updatedAt,

app/Enums/Permissions.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace App\Enums;
4+
5+
use App\Concerns\EnumDisplayArray;
6+
7+
enum Permissions: string
8+
{
9+
use EnumDisplayArray;
10+
11+
case CreateUsers = 'create users';
12+
case EditUsers = 'edit users';
13+
case DeleteUsers = 'delete users';
14+
case ListUsers = 'list users';
15+
case ViewUsers = 'view users';
16+
case ListCurriculum = 'list curriculum';
17+
case ListClasses = 'list classes';
18+
case ListReports = 'list reports';
19+
20+
public function displayValue(): string
21+
{
22+
return match ($this) {
23+
self::CreateUsers => 'Create Users',
24+
self::EditUsers => 'Edit Users',
25+
self::DeleteUsers => 'Delete Users',
26+
self::ListUsers => 'List Users',
27+
self::ViewUsers => 'View Users',
28+
self::ListCurriculum => 'List Curriculum',
29+
self::ListClasses => 'List Classes',
30+
self::ListReports => 'List Reports',
31+
default => $this->defaultDisplayValue(),
32+
};
33+
}
34+
35+
}

app/Enums/Roles.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace App\Enums;
4+
5+
use App\Concerns\EnumDisplayArray;
6+
7+
enum Roles: string
8+
{
9+
use EnumDisplayArray;
10+
case Admin = 'admin';
11+
case Director = 'director';
12+
case RegionDirector = 'region director';
13+
case ProgramDirector = 'program director';
14+
case Facilitator = 'facilitator';
15+
case Auditor = 'auditor';
16+
case Intake = 'intake';
17+
case Participant = 'participant';
18+
19+
public function displayValue(): string
20+
{
21+
return match ($this) {
22+
self::Admin => 'Admin',
23+
self::Director => 'Director',
24+
self::RegionDirector => 'Region Director',
25+
self::ProgramDirector => 'Program Director',
26+
self::Facilitator => 'Facilitator',
27+
self::Auditor => 'Auditor',
28+
self::Intake => 'Intake',
29+
self::Participant => 'Participant',
30+
default => $this->defaultDisplayValue(),
31+
};
32+
}
33+
}

app/Http/Controllers/Intake/ParticipantRegistrationController.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Http\Controllers\Intake;
44

5+
use App\Enums\Roles;
56
use App\Http\Controllers\Controller;
67
use App\Models\User;
78
use App\Rules\UsPhoneNumber;
@@ -30,7 +31,7 @@ public function create(): Response
3031
* @throws \Illuminate\Validation\ValidationException
3132
*/
3233
public function store(Request $request): RedirectResponse
33-
{
34+
{
3435
$request->validate([
3536
'first_name' => 'required|string|max:255',
3637
'last_name' => 'required|string|max:255',
@@ -48,7 +49,7 @@ public function store(Request $request): RedirectResponse
4849
'password' => Hash::make($request->password),
4950
]);
5051

51-
$user->assignRole('participant');
52+
$user->assignRole(Roles::Participant);
5253

5354
event(new Registered($user));
5455

app/Http/Controllers/UsersController.php

Lines changed: 158 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,34 @@
33
namespace App\Http\Controllers;
44

55
use App\Data\UserData;
6+
use App\Enums\Permissions;
7+
use App\Enums\Roles;
68
use App\Models\User;
9+
use Illuminate\Http\RedirectResponse;
710
use Illuminate\Http\Request;
11+
use Illuminate\Validation\Rules;
812
use Inertia\Inertia;
913
use Inertia\Response;
14+
use Illuminate\Support\Facades\Hash;
15+
use Illuminate\Validation\Rule;
1016

1117
class UsersController extends Controller
1218
{
19+
private array $userValidationRules;
20+
21+
public function __construct()
22+
{
23+
$this->userValidationRules = [
24+
'first_name' => ['required', 'string', 'max:255'],
25+
'last_name' => ['required', 'string', 'max:255'],
26+
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:users'],
27+
'phone_number' => ['required', 'string', 'max:12'],
28+
'password' => ['required', Rules\Password::defaults()],
29+
'active' => ['boolean'],
30+
'roles' => ['required', 'array'],
31+
];
32+
}
33+
1334
public function list(Request $request): Response
1435
{
1536
$page = intval($request->get('page', 1) ?: 1);
@@ -36,41 +57,145 @@ public function list(Request $request): Response
3657
]);
3758
}
3859

39-
// public function show(User $user): Response
40-
// {
41-
// return Inertia::render('Users/Show', [
42-
// 'user' => new UserResource($user),
43-
// ]);
44-
// }
45-
//
46-
// public function edit(User $user): Response
47-
// {
48-
// return Inertia::render('Users/Edit', [
49-
// 'user' => new UserResource($user),
50-
// ]);
51-
// }
52-
//
53-
// public function update(User $user): Response
54-
// {
55-
// $user->update(
56-
// request()->validate([
57-
// 'name' => ['required', 'max:50'],
58-
// 'email' => ['required', 'email'],
59-
// ])
60-
// );
61-
//
62-
// return redirect()->route('users.index');
63-
// }
64-
//
65-
// public function destroy(User $user)
66-
// {
67-
// $user->delete();
68-
//
69-
// return redirect()->route('users.index');
70-
// }
60+
/**
61+
* Display the specified user.
62+
*/
63+
public function show(User $user): Response
64+
{
65+
if (! auth()->user()->hasPermissionTo(Permissions::ViewUsers)) {
66+
abort(403, 'You do not have permission to view user details.');
67+
}
68+
69+
return Inertia::render('Users/Show', [
70+
'user' => UserData::from($user),
71+
]);
72+
}
73+
74+
public function destroy(User $user): RedirectResponse
75+
{
76+
if (! auth()->user()->hasPermissionTo(Permissions::DeleteUsers)) {
77+
return back()->withErrors(['error' => 'You do not have permission to delete users.']);
78+
}
79+
if (auth()->id() === $user->id) {
80+
return back()->withErrors(['error' => 'You cannot delete your own account.']);
81+
}
82+
83+
$user->delete();
84+
85+
return redirect()->route('users.list')->with('toast', [
86+
'type' => 'success',
87+
'message' => "User {$user->first_name} {$user->last_name} was successfully deleted.",
88+
]);
89+
}
90+
91+
public function destroyMultiple(Request $request): RedirectResponse
92+
{
93+
if (! auth()->user()->hasPermissionTo(Permissions::DeleteUsers)) {
94+
return back()->withErrors(['error' => 'You do not have permission to delete users.']);
95+
}
96+
97+
$userIds = $request->input('user_ids', []);
98+
99+
if (empty($userIds)) {
100+
return back()->withErrors(['error' => 'No users specified for deletion.']);
101+
}
102+
103+
// Prevent deleting your own account
104+
if (in_array(auth()->id(), $userIds)) {
105+
return back()->withErrors(['error' => 'You cannot delete your own account.']);
106+
}
107+
108+
$users = User::whereIn('id', $userIds)->get();
109+
$count = $users->count();
110+
111+
// Delete the users
112+
User::whereIn('id', $userIds)->delete();
113+
114+
return back()->with('toast', [
115+
'type' => 'success',
116+
'message' => "{$count} ".($count === 1 ? 'user' : 'users').' successfully deleted.',
117+
]);
118+
}
71119

72120
public function create(): Response
73121
{
74-
return Inertia::render('Users/Create');
122+
if (! auth()->user()->hasPermissionTo(Permissions::CreateUsers)) {
123+
abort(403, 'You do not have permission to create users.');
124+
}
125+
126+
return Inertia::render('Users/Create', [
127+
'roles' => Roles::cases(),
128+
]);
129+
}
130+
131+
public function edit(User $user): Response
132+
{
133+
if (! auth()->user()->hasPermissionTo(Permissions::EditUsers)) {
134+
abort(403, 'You do not have permission to edit users.');
135+
}
136+
137+
return Inertia::render('Users/Edit', [
138+
'user' => UserData::from($user),
139+
'roles' => Roles::cases(),
140+
]);
141+
}
142+
143+
public function store(Request $request): RedirectResponse
144+
{
145+
if (! auth()->user()->hasPermissionTo(Permissions::CreateUsers)) {
146+
return back()->withErrors(['error' => 'You do not have permission to create users.']);
147+
}
148+
149+
$request->validate($this->userValidationRules);
150+
151+
$user = User::create([
152+
'first_name' => $request->first_name,
153+
'last_name' => $request->last_name,
154+
'email' => $request->email,
155+
'phone_number' => $request->phone_number,
156+
'password' => Hash::make($request->password),
157+
'active' => $request->active,
158+
]);
159+
160+
$user->syncRoles($request->roles);
161+
162+
return redirect()->route('users.list')->with('toast', [
163+
'type' => 'success',
164+
'message' => "User {$user->first_name} {$user->last_name} was successfully created.",
165+
]);
166+
}
167+
168+
public function update(Request $request, User $user): RedirectResponse
169+
{
170+
if (! auth()->user()->hasPermissionTo(Permissions::EditUsers)) {
171+
return back()->withErrors(['error' => 'You do not have permission to edit users.']);
172+
}
173+
174+
$rules = $this->userValidationRules;
175+
$rules['email'] = ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique('users')->ignore($user->id)];
176+
$rules['password'] = ['nullable', Rules\Password::defaults()];
177+
178+
$request->validate($rules);
179+
180+
$user->update([
181+
'first_name' => $request->first_name,
182+
'last_name' => $request->last_name,
183+
'email' => $request->email,
184+
'phone_number' => $request->phone_number,
185+
'active' => $request->active,
186+
]);
187+
188+
if ($request->password) {
189+
$user->update([
190+
'password' => Hash::make($request->password),
191+
]);
192+
}
193+
194+
$user->syncRoles($request->roles);
195+
196+
return redirect()->route('users.list')->with('toast', [
197+
'type' => 'success',
198+
'message' => "User {$user->first_name} {$user->last_name} was successfully updated.",
199+
]);
75200
}
76201
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Illuminate\Auth\Middleware\Authenticate as Middleware;
6+
use Illuminate\Http\Request;
7+
8+
class Authenticate extends Middleware
9+
{
10+
/**
11+
* Get the path the user should be redirected to when they are not authenticated.
12+
*/
13+
protected function redirectTo(Request $request): ?string
14+
{
15+
// For API requests, return null (will result in a 401 response)
16+
if ($request->expectsJson()) {
17+
return null;
18+
}
19+
20+
// For web requests, redirect to home where the login page is rendered
21+
return route('home');
22+
}
23+
}

app/Http/Middleware/HandleInertiaRequests.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public function share(Request $request): array
3939
'location' => $request->url(),
4040
'query' => $request->query() ?? [],
4141
],
42+
'toast' => $request->session()->get('toast'),
4243
];
4344
}
4445
}

0 commit comments

Comments
 (0)