Adding user handling pages for admins.
This commit is contained in:
parent
c53a4155f4
commit
bd21a64191
9 changed files with 485 additions and 0 deletions
38
app/Http/Controllers/Admin/UserController.php
Normal file
38
app/Http/Controllers/Admin/UserController.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Http\Requests\Admin\UserRequest;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('admin.user.index');
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('admin.user.form');
|
||||
}
|
||||
|
||||
public function edit(User $user)
|
||||
{
|
||||
return view('admin.user.form', [
|
||||
'model' => $user
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user.
|
||||
*/
|
||||
public function destroy(User $user)
|
||||
{
|
||||
$user->delete();
|
||||
|
||||
return redirect()->route('admin.users.index')
|
||||
->with(['success' => "<strong>{$user->username}</strong> was deleted!"]);
|
||||
}
|
||||
}
|
||||
44
app/Http/Livewire/AdminUserTable.php
Normal file
44
app/Http/Livewire/AdminUserTable.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class AdminUserTable extends Component
|
||||
{
|
||||
use Traits\WithPagination;
|
||||
|
||||
protected $queryString = [
|
||||
'username' => ['except' => ''],
|
||||
'role' => ['except' => '']
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter by username
|
||||
*/
|
||||
public $username;
|
||||
|
||||
/**
|
||||
* Filter by role
|
||||
*/
|
||||
public $role;
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = User::query()->orderBy('username');
|
||||
|
||||
if (strlen($this->username) >= 3) {
|
||||
$query->where('username', 'LIKE', '%' . $this->username . '%');
|
||||
}
|
||||
|
||||
if (in_array($this->role, ['user','admin'])) {
|
||||
$query->where('role', $this->role);
|
||||
}
|
||||
|
||||
return view('livewire.admin-user-table', [
|
||||
'users' => $query->paginate($this->perPage)
|
||||
]);
|
||||
}
|
||||
}
|
||||
83
app/Http/Livewire/Form/Admin/UserForm.php
Normal file
83
app/Http/Livewire/Form/Admin/UserForm.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Form\Admin;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class UserForm extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
/**
|
||||
* User object.
|
||||
*/
|
||||
public User $user;
|
||||
|
||||
/**
|
||||
* Password field
|
||||
*/
|
||||
public $password;
|
||||
|
||||
/**
|
||||
* Password confirmation field.
|
||||
*/
|
||||
public $password_confirmation;
|
||||
|
||||
/**
|
||||
* Initialize the component.
|
||||
*/
|
||||
public function mount(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'user.username' => ['required', 'min:4', Rule::unique('users', 'username')->ignore($this->user) ],
|
||||
'user.role' => 'required|in:user,admin',
|
||||
'password' => ($this->user->exists ? 'nullable' : 'required') . '|min:8|confirmed',
|
||||
];
|
||||
}
|
||||
|
||||
public function updated($property)
|
||||
{
|
||||
$this->validateOnly($property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the user
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$this->authorize('administrate');
|
||||
|
||||
$this->validate();
|
||||
|
||||
if ($this->password) {
|
||||
$this->user->password = Hash::make($this->password);
|
||||
}
|
||||
$this->user->save();
|
||||
|
||||
// Livewire redirect() does not have "with" method.
|
||||
// so we call session()->flash() directly instead.
|
||||
session()->flash('success', "<strong>{$this->user->username}</strong> was "
|
||||
. ($this->user->exists ? "updated!" : "created!"));
|
||||
return redirect()->route('admin.users.index');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.form.admin.user');
|
||||
}
|
||||
}
|
||||
11
resources/views/admin/user/form.blade.php
Normal file
11
resources/views/admin/user/form.blade.php
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<x-layout name="app">
|
||||
|
||||
<x-slot name="title">{{ __('Admin') }} - {{ __('Users') }} - {{ __(isset($model) ? 'Edit' : 'New') }}</x-slot>
|
||||
|
||||
@if (isset($model))
|
||||
<livewire:form.admin.user-form :user="$model" />
|
||||
@else
|
||||
<livewire:form.admin.user-form />
|
||||
@endif
|
||||
|
||||
</x-layout>
|
||||
13
resources/views/admin/user/index.blade.php
Normal file
13
resources/views/admin/user/index.blade.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<x-layout name="app">
|
||||
|
||||
<x-slot name="title">{{ __('Admin') }} - {{ __('Users') }}</x-slot>
|
||||
|
||||
<x-slot name="page_links">
|
||||
<x-button element="a" class="flex items-center" href="{{ route('admin.users.create') }}">
|
||||
{{ __('New') }}
|
||||
</x-button>
|
||||
</x-slot>
|
||||
|
||||
<livewire:admin-user-table />
|
||||
|
||||
</x-layout>
|
||||
42
resources/views/livewire/admin-user-table.blade.php
Normal file
42
resources/views/livewire/admin-user-table.blade.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<div class="p-4">
|
||||
|
||||
<table class="w-full">
|
||||
|
||||
<tr>
|
||||
<th><x-input wire:model="username" name="username" placeholder="Username" /></th>
|
||||
<th><x-select wire:model="role" name="role" :options="['' => '-- Role --', 'user' => 'User', 'admin' => 'Admin']" /></th>
|
||||
</tr>
|
||||
|
||||
<tr class="border-b-2">
|
||||
<th class="py-2 text-left">Username</th>
|
||||
<th class="py-2 w-24">Role</th>
|
||||
<th class="py-2 w-44">Created</th>
|
||||
<th class="py-2 w-44">Updated</th>
|
||||
<th class="py-2 w-6"> </th>
|
||||
</tr>
|
||||
|
||||
@foreach($users as $user)
|
||||
<tr class="border-b hover:bg-gray-100">
|
||||
<td class="px-2 py-1">
|
||||
<x-link href="{{ route('admin.users.edit', [ 'user' => $user ]) }}">
|
||||
{{ $user->username }}
|
||||
</x-link>
|
||||
</td>
|
||||
<td class="px-2 py-1 text-center">{{ $user->role }}</td>
|
||||
<td class="px-2 py-1">{{ $user->created_at }}</td>
|
||||
<td class="px-2 py-1">{{ $user->updated_at }}</td>
|
||||
<td>
|
||||
<x-form :action="route('admin.users.destroy', [ 'user' => $user ])" method="DELETE">
|
||||
<a href="#" onclick="this.closest('form').submit();return false;">
|
||||
<x-icon name="cross" class="h-6 h-6 text-danger-400 hover:text-danger-600" />
|
||||
</a>
|
||||
</x-form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ $users->links() }}
|
||||
</div>
|
||||
</div>
|
||||
27
resources/views/livewire/form/admin/user.blade.php
Normal file
27
resources/views/livewire/form/admin/user.blade.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<x-form class="p-4" wire:submit.prevent="save">
|
||||
|
||||
<div>
|
||||
<x-form.label for="username">{{ __('Username') }}</x-form.label>
|
||||
<x-input wire:model="user.username" name="user.username" label="username" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-form.label for="role">{{ __('Role') }}</x-form.label>
|
||||
<x-select wire:model="user.role" name="role" :options="['user' => 'User', 'admin' => 'Admin']" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-2">
|
||||
<x-form.label for="password">{{ __('Password') }}</x-form.label>
|
||||
<x-input-password wire:model="password" name="password" />
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<x-form.label for="password_confirmation">{{ __('Confirm Password') }}</x-form.label>
|
||||
<x-input-password wire:model="password_confirmation" name="password_confirmation" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<x-button element="input" type="submit" value="{{ $user->id ? 'Save' : 'Create' }}" />
|
||||
</div>
|
||||
</x-form>
|
||||
|
|
@ -10,6 +10,8 @@ use App\Http\Controllers\RecipeController;
|
|||
|
||||
use App\Http\Controllers\Auth\SessionController;
|
||||
|
||||
use App\Http\Controllers\Admin\UserController as AdminUserController;
|
||||
|
||||
require "oauth.php";
|
||||
|
||||
/*
|
||||
|
|
@ -70,4 +72,10 @@ Route::middleware(['auth'])->group(function() {
|
|||
Route::get('/edit', [UserController::class, 'edit'])->name('edit');
|
||||
Route::post('/', [UserController::class, 'update'])->name('update');
|
||||
});
|
||||
|
||||
// Admin
|
||||
Route::middleware(['can:administrate'])->prefix('admin')->name('admin.')->group(function () {
|
||||
|
||||
Route::resource('users', AdminUserController::class)->except(['show', 'store', 'update']);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
219
tests/Feature/Admin/UserTest.php
Normal file
219
tests/Feature/Admin/UserTest.php
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Admin;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Http\Livewire\Form\Admin\UserForm;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_admin_can_view_users_list()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('admin.users.index'));
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_non_admin_cannot_view_users_list()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'user']);
|
||||
|
||||
// Standard user
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('admin.users.index'));
|
||||
|
||||
$response->assertForbidden("Standard user");
|
||||
|
||||
// Guest
|
||||
$response = $this->get(route('admin.users.index'));
|
||||
|
||||
$response->assertForbidden("Guest");
|
||||
}
|
||||
|
||||
public function test_admin_can_render_create_page()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('admin.users.create'));
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_non_admin_cannot_render_create_page()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'user']);
|
||||
|
||||
// Standard user
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('admin.users.create'));
|
||||
|
||||
$response->assertForbidden("Standard user");
|
||||
|
||||
// Guest
|
||||
$response = $this->get(route('admin.users.create'));
|
||||
|
||||
$response->assertForbidden("Guest");
|
||||
}
|
||||
|
||||
public function test_admin_can_create_user()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'admin']);
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
\Livewire::test(UserForm::class)
|
||||
->set('user.username', 'scammer123')
|
||||
->set('user.role', 'user')
|
||||
->set('password', 'password1234')
|
||||
->set('password_confirmation', 'password1234')
|
||||
->call('save')
|
||||
->assertRedirect(route('admin.users.index'));
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'username' => 'scammer123',
|
||||
'role' => 'user'
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_non_admin_cannot_create_users()
|
||||
{
|
||||
// Guest
|
||||
\Livewire::test(UserForm::class)
|
||||
->set('user.username', 'nonadmin')
|
||||
->set('user.role', 'user')
|
||||
->set('password', 'password1234')
|
||||
->set('password_confirmation', 'password1234')
|
||||
->call('save')
|
||||
->assertForbidden();
|
||||
|
||||
$this->assertDatabaseMissing('users', ['username' => 'nonadmin']);
|
||||
|
||||
// Standard user
|
||||
$this->actingAs(User::factory()->create(['role' => 'user']));
|
||||
|
||||
\Livewire::test(UserForm::class)
|
||||
->set('user.username', 'nonadmin')
|
||||
->set('user.role', 'user')
|
||||
->set('password', 'password1234')
|
||||
->set('password_confirmation', 'password1234')
|
||||
->call('save')
|
||||
->assertForbidden();
|
||||
|
||||
$this->assertDatabaseMissing('users', ['username' => 'nonadmin']);
|
||||
}
|
||||
|
||||
public function test_admin_can_render_edit_page()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'admin']);
|
||||
$edit = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('admin.users.edit', ['user' => $edit]));
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function test_non_admin_cannot_render_edit_page()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'user']);
|
||||
$edit = User::factory()->create();
|
||||
|
||||
// Standard user
|
||||
$response = $this->actingAs($user)
|
||||
->get(route('admin.users.edit', ['user' => $edit]));
|
||||
|
||||
$response->assertForbidden("Standard user");
|
||||
|
||||
// Guest
|
||||
$response = $this->get(route('admin.users.edit', ['user' => $edit]));
|
||||
|
||||
$response->assertForbidden("Guest");
|
||||
}
|
||||
|
||||
public function test_admin_can_edit_user()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'admin']);
|
||||
$edit = User::factory()->create(['role' => 'user']);
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
\Livewire::test(UserForm::class, [ 'user' => $edit ])
|
||||
->set('user.username', 'Edited')
|
||||
->call('save')
|
||||
->assertRedirect(route('admin.users.index'));
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'username' => 'Edited',
|
||||
'role' => 'user',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_non_admin_cannot_edit_user()
|
||||
{
|
||||
$edit = User::factory()->create(['username' => 'Untouched', 'role' => 'user']);
|
||||
|
||||
// Guest
|
||||
\Livewire::test(UserForm::class, [ 'user' => $edit ])
|
||||
->set('user.username', 'Cantedit')
|
||||
->call('save')
|
||||
->assertForbidden();
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'username' => 'Untouched',
|
||||
'role' => 'user',
|
||||
]);
|
||||
|
||||
// Standard user
|
||||
$this->actingAs(User::factory()->create(['role' => 'user']));
|
||||
|
||||
\Livewire::test(UserForm::class, [ 'user' => $edit ])
|
||||
->set('user.username', 'Cantedit')
|
||||
->call('save')
|
||||
->assertForbidden();
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'username' => 'Untouched',
|
||||
'role' => 'user',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_admin_can_delete_user()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'admin']);
|
||||
$delete = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->delete(route('admin.users.destroy', [ 'user' => $delete ]));
|
||||
|
||||
$this->assertDatabaseMissing('users', [ 'id' => $delete->id, 'deleted_at' => NULL ]);
|
||||
}
|
||||
|
||||
public function test_non_admin_cannot_delete_user()
|
||||
{
|
||||
$user = User::factory()->create(['role' => 'user']);
|
||||
$delete = User::factory()->create();
|
||||
|
||||
// Standard user
|
||||
$response = $this->actingAs($user)
|
||||
->delete(route('admin.users.destroy', [ 'user' => $delete ]));
|
||||
|
||||
$response->assertForbidden("Standard user");
|
||||
$this->assertDatabaseHas('users', [ 'id' => $delete->id, 'deleted_at' => NULL ]);
|
||||
|
||||
// Guest
|
||||
$response = $this->delete(route('admin.users.destroy', [ 'user' => $delete ]));
|
||||
|
||||
$response->assertForbidden("Guest");
|
||||
$this->assertDatabaseHas('users', [ 'id' => $delete->id, 'deleted_at' => NULL ]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue