Laravel Ecommerce Tutorial: Part 2, Users And Authorization
Published: 2023-01-12 12:43:38

In the [previous post,](https://flixtechs.co.zw/posts/laravel-ecommerce-tutorial-part-1-introduction) we set up our project and installed basic tooling to help with our development. This is the second post on our ongoing series of developing an ecommerce website with Laravel.

In this post we are going to setup user authentication with [Laravel Fortify](https://laravel.com/docs/9.x/fortify), setup authorization with [Spatie Laravel Permission](https://spatie.be/docs/laravel-permission/v5/introduction) with the help of our very own [Laravel Authorizer](https://github.com/flixtechs-labs/laravel-authorizer). Let's get started!

## Authentication

We are not going to use a starter kit for our user authentication, instead we are going to setup Laravel Fortify from scratch. To get started, let's install Laravel Fortify.

```bash
composer require laravel/fortify
```

Next, let's publish Fortify's resources using the `vendor:publish` command:

```bash
php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
```

This command will publish Fortify's actions to your `app/Actions` directory, which will be created if it does not exist. In addition, the `FortifyServiceProvider`, configuration file, and all necessary database migrations will be published.

Next, you should migrate your database:

```bash
php artisan migrate
```

Next, add the `FortifyServiceProvider` to your `providers` array in your `config/app.php` file.

Next, we need to create our auth views. Now, we will be using Stisla admin template. Let's start by installing bootstrap css

```bash
yarn add --dev @popperjs/core bootstrap
```

In order to compile our bootstrap css let's add sass

```bash
yarn add --dev sass
```

Next, let's create a `resources/sass/app.scss` file and add the following

```css
@import "variables";
@import "bootstrap/scss/bootstrap";
```

Next, create an empty `resources/sass/_variables.scss`

Now let's tell vite how to handle our sass, define your `vite.config.js` as follows

```javascript
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/sass/app.scss', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
});
```

Next, let's import the Stisla admin template. Go ahead and clone Stisla from GitHub.

```bash
git clone https://github.com/stisla/stisla.git
```

After cloning the repo, copy everything in `src/scss/*` to your `resources/sass` directory

After that in your `resources/sass/app.scss` add the following

```bash
@import "style";
```

Next we need to create our views. But first we need a way to generate views with just one command. Let's install this package to help with that:

```bash
composer require 'maddhatter/laravel-view-generator:dev-master' --dev
```

After installing, let's create our `auth layout` with

```bash
php artisan make:view layouts.auth
```

In the layout file add the following

```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no"
          name="viewport">
    <title>Login - Ecommerce</title>

    <link rel="stylesheet"
          href="https://use.fontawesome.com/releases/v5.7.2/css/all.css"
          integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr"
          crossorigin="anonymous">

    @vite(['resources/sass/app.scss', 'resources/js/app.js'])

</head>

<body>
    <div id="app">
        @yield('content')
    </div>
</body>

</html>
```

Next, create the `resources/auth/login.blade.php` and add the following

```html
@extends('layouts.auth')

@section('content')
   <section class="">
        <div class="d-flex flex-wrap align-items-center">
            <div class="col-lg-4 col-md-6 col-12 order-lg-1 min-vh-100 order-2 bg-white">
                <div class="p-4 m-3 mt-md-5 py-md-5">
                    <h4 class="text-dark font-weight-normal">Welcome to <span class="fw-bold">Ecommerce</span></h4>
                    <p class="text-muted">
						Before you get started, you must login or register if you don't already have an
                        account.
					</p>

					<form method="POST"
                          action="{{ route('login') }}"
                          class="needs-validation"
                          novalidate="">
                        @csrf
                        <div class="form-group">
                            <label for="email">Email</label>
                            <input id="email"
                                   type="email"
                                   class="form-control @error('email') is-invalid @enderror"
                                   name="email"
                                   tabindex="1"
                                   required
                                   autofocus>
                            @error('email')
                                <div class="invalid-feedback">
                                    Please fill in your email
                                </div>
                            @enderror
                        </div>

                        <div class="form-group">
                            <div class="d-block">
                                <label for="password"
                                       class="control-label">Password</label>
                            </div>
                            <input id="password"
                                   type="password"
                                    class="form-control @error('password') is-invalid @enderror"
                                   name="password"
                                   tabindex="2"
                                   required>
                            @error('password')
                                <div class="invalid-feedback">
                                    please fill in your password
                                </div>
                            @enderror
                        </div>

                        <div class="form-group">
                            <div class="custom-control custom-checkbox">
                                <input type="checkbox"
                                       name="remember"
                                       class="custom-control-input @error('remember') is-invalid @enderror"
                                       tabindex="3"
                                       id="remember-me">
                                <label class="custom-control-label"
                                       for="remember-me">Remember Me</label>

                            </div>
                        </div>

                        <div class="form-group text-end">
                            <a href="{{ route('password.request') }}"
                               class="float-start mt-3">
                                Forgot Password?
                            </a>
                            <button type="submit"
                                    class="btn btn-primary btn-lg btn-icon icon-right"
                                    tabindex="4">
                                Login
                            </button>
                        </div>

                        <div class="mt-5 text-center">
                            Don't have an account? <a href="{{ route('register') }}">Create new one</a>
                        </div>
                    </form>
                   

                    <div class="text-center mt-5 text-small">
                        Copyright {{ date('Y') }} Ecommerce. All rights reserved.
                    </div>
                </div>
            </div>
            <div class="col-lg-8 col-12 order-lg-2 order-1 min-vh-100 background-walk-y position-relative overlay-gradient-bottom"
                 style="background-image: url('https://images.unsplash.com/photo-1672862817339-51ef2610a5d0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2109&q=80'); background-size: cover">
                <div class="absolute-bottom-left index-2">
                    <div class="text-light p-5 pb-2">
                        <div class="mb-5 pb-3">
                            <h1 class="mb-2 display-4 font-weight-bold">Good Morning</h1>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>
@endsection
```

Let's tell Fortify how to return the login response. In the `FortifyServiceProvider` boot method

```php
Fortify::loginView(static function () {
	return view('auth.login');
});
```

Use the Stisla templates to create other auth views, `forgot-password`, `password reset`, etc

## Authorization

Now, after creating our auth views it's time to setup authorization.

To make authorization easier let's add our very own laravel-authorizer

```bash
composer require flixtechs-labs/laravel-authorizer
```

And set it up by running

```bash
php artisan authorizer:setup
```

In the `User` model add the following trait

```php
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
...
```

We need a way to create the `super admin` user. We can do that by using an artisan command. Let's create the command.

```bash
php artisan make:command MakeAdmin --test --command="make:admin"
```

Since we are responsible developers, let's start by writing a test for our command. In the `tests/Feature/Console/Commands/MakeAdminTest.php` add the following

```php
<?php

namespace Tests\Feature\Console\Commands;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class MakeAdminTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function test_can_make_admin_user_even_if_user_does_not_exist(): void
    {
        $this->artisan('make:admin')
            ->expectsQuestion(
                'What is the email of the user you want to make admin?',
                'admin@example.com',
            )
            ->expectsQuestion('What is their name?', 'Admin')
            ->expectsQuestion(
                'What is the password of the user you want to make admin?',
                'password',
            )
            ->expectsQuestion(
                'Please confirm the password of the user you want to make admin?',
                'password',
            )
            ->expectsOutput(
                'User admin@example.com now has full access to your site.',
            );

        $this->assertDatabaseHas('users', [
            'email' => 'admin@example.com',
        ]);

        $this->assertContains(
            'super admin',
            User::where('email', 'admin@example.com')
                ->first()
                ->roles->pluck('name'),
        );
    }

    public function test_can_make_admin_if_user_exists(): void
    {
        $user = User::factory()->create();

        $this->artisan('make:admin')
            ->expectsQuestion(
                'What is the email of the user you want to make admin?',
                $user->email,
            )
            ->expectsOutput(
                'User ' . $user->email . ' now has full access to your site.',
            );
    }
}
```

Let's proceed to write our command as follows

```php
<?php

namespace App\Console\Commands;

use App\Actions\Fortify\PasswordValidationRules;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Spatie\Permission\Models\Role;

class MakeAdmin extends Command
{
    use PasswordValidationRules;

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'make:admin';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a user with super admin access';

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle(): int
    {
        $user = $this->getUserInfo();

        if (!$user) {
            return 0;
        }

        $this->assignRole($user);

        $this->info(
            'User ' . $user->email . ' now has full access to your site.',
        );

        return 1;
    }

    /**
     * Get the user information from the user.
     *
     * @return bool|User
     */
    public function getUserInfo(): bool|User
    {
        $email = $this->ask(
            'What is the email of the user you want to make admin?',
        );

        $user = User::where('email', $email)->first();

        if (!is_null($user)) {
            return $user;
        }

        $name = $this->ask('What is their name?');
        $password = $this->secret(
            'What is the password of the user you want to make admin?',
        );

        $passwordConfirmation = $this->secret(
            'Please confirm the password of the user you want to make admin?',
        );

        $validator = Validator::make(
            [
                'name' => $name,
                'email' => $email,
                'password' => $password,
                'password_confirmation' => $passwordConfirmation,
            ],
            [
                'name' => ['required', 'string', 'max:255'],
                'email' => [
                    'required',
                    'string',
                    'email',
                    'max:255',
                    'unique:users',
                ],
                'password' => $this->passwordRules(),
            ],
        );

        if ($validator->fails()) {
            $this->error('Operation failed. Please check errors below:');

            foreach ($validator->errors()->all() as $error) {
                $this->error($error);
            }

            return false;
        }

        return User::create([
            ...$validator->validated(),
            'password' => Hash::make($password),
        ]);
    }

    /**
     * Assign the super admin role to the user
     *
     * @param User $user
     * @return void
     */
    public function assignRole(User $user): void
    {
        $role = Role::findOrCreate('super admin');
        $user->assignRole($role);
    }
}
```

The above code first prompts for an email address. If the user with that email address exists, it then assigns the `super admin` role to that user, else it asks for the name and password and creates a new user who is then assigned the `super admin` role.

Now go ahead and create your first super admin user

```bash
php artisan make:admin
```

Next let's configure our gates to allow anywhere anyone with a `super admin` role.

In your `AuthServiceProvider`

```php
/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
 public function boot()
 {
	$this->registerPolicies();

	Gate::before(static function ($user, $ability) {
		return $user->hasRole('super admin') ? true : null;
	});
 }
```

At this point, we have set up our authentication with Laravel Forty, our authorization is ready and at this point allows anyone with a `super admin` role in. Now let's create a basic dashboard home page.

Let's start by creating our controller

```bash
php artisan make:controller Admin\\HomeController --test
```

Like the good developers we are, let's write some tests for the `HomeController`. In the `tests/Feature/Http/Controllers/Admin/HomeControllerTest.php` add the following

```php
<?php

namespace Tests\Feature\Http\Controllers\Admin;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

class HomeControllerTest extends TestCase
{
    use RefreshDatabase;

    /**
     * Test that the admin home page is accessible to super admins.
     *
     * @return void
     */
    public function test_can_see_admin_home_page_if_user_is_super_admin(): void
    {
        $this->actingAs($this->superAdminUser())
            ->get(route('admin.home.index'))
            ->assertStatus(200)
            ->assertViewIs('admin.home.index')
            ->assertSee('Dashboard');
    }

    /**
     * Test that a user cannot see the admin home page if they are not a super admin.
     *
     * @return void
     */
    public function test_cannot_see_admin_home_page_if_user_is_not_super_admin(): void
    {
        $this->actingAs($this->user())
            ->get(route('admin.home.index'))
            ->assertForbidden();
    }

    /**
     * Test that the user is redirected to the login page if they are not logged in.
     *
     * @return void
     */
    public function test_cannot_see_admin_home_page_if_user_is_not_logged_in(): void
    {
        $this->get(route('admin.home.index'))->assertRedirect(route('login'));
    }

    /**
     * Test that the admin home page is accessible if user has the permission
     *
     * @return void
     */
    public function test_can_see_admin_home_page_if_user_has_permission(): void
    {
        $user = $this->user();
        $user->givePermissionTo(
            Permission::findOrCreate('view admin dashboard'),
        );

        $this->actingAs($user)
            ->get(route('admin.home.index'))
            ->assertStatus(200)
            ->assertViewIs('admin.home.index')
            ->assertSee('Dashboard');
    }
}
```

And in your `tests/TestCase.php`

```php
<?php

namespace Tests;

use App\Models\User;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Spatie\Permission\Models\Role;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

    /**
     * Create a super admin user.
     *
     * @return User
     */
    protected function superAdminUser(): User
    {
        $user = User::factory()->create();
        $user->assignRole(Role::findOrCreate('super admin'));

        return $user;
    }

    /**
     * Create a user.
     *
     * @return User
     */
    protected function user(): User
    {
        return User::factory()->create();
    }
}
```

To pass the first test `test_can_see_admin_home_page_if_user_is_super_admin` add the following route in the `routes/web.php` file

```php
Route::prefix('admin')
    ->middleware(['auth', 'permission:view admin dashboard'])
	->name('admin.')
    ->group(static function () {
        Route::get('/', [HomeController::class, 'index'])->name('home.index');
    });
```

Then register the following middleware from spate/laravel-permission in your `Kernel.php` file like this

```php
protected $routeMiddleware = [
    // ...
    'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
    'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
    'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class,
];
```

## Setting up the layout

Next let's create our layout

```bash
php artisan make:view layouts.app
```

And add the following

```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no"
          name="viewport">
    <meta name="csrf-token"
          content="{{ csrf_token() }}">
    <title>Ecommerce Dashboard - @yield('title')</title>

    <link rel="stylesheet"
          href="https://use.fontawesome.com/releases/v5.7.2/css/all.css"
          integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr"
          crossorigin="anonymous">

    @vite(['resources/sass/app.scss', 'resources/js/app.js'])
</head>

<body>
    <div id="app">
        <div class="main-wrapper">
            <div class="navbar-bg"></div>

            @include('layouts.partials.navbar')

            @include('layouts.partials.sidebar')

            <div class="main-content">
                @yield('content')
            </div>

            @include('layouts.partials.footer')
        </div>
    </div>
</body>

</html>
```

And then the navbar partial

```bash
php artisan make:view layouts.partials.navbar
```

and define it as follows

```html
<nav class="navbar navbar-expand-lg main-navbar">

    <ul class="navbar-nav navbar-right ms-auto">

        <li class="dropdown dropdown-list-toggle">
            <a href="#"
               data-toggle="dropdown"
               class="nav-link notification-toggle nav-link-lg">
                <i class="far fa-bell"></i>
            </a>
            <div class="dropdown-menu dropdown-list dropdown-menu-right">

            </div>
        </li>

        <li class="dropdown">
            <a href="#"
               data-bs-toggle="dropdown"
               class="nav-link dropdown-toggle nav-link-lg nav-link-user">
                <div class="d-sm-none d-lg-inline-block">Hi, {{ auth()->user()->name }}</div>
            </a>
            <div class="dropdown-menu dropdown-menu-right">
                <div class="dropdown-title">Logged in 5 min ago</div>
                <a href="#"
                   class="dropdown-item has-icon">
                    <i class="far fa-user"></i> Profile
                </a>

                <div class="dropdown-divider"></div>

                <form action="{{ route('logout') }}"
                      method="post"
                      id="logout">
                    @csrf
                    <a onclick="document.getElementById('logout').requestSubmit()"
                       data-turbo-method="post"
                       class="dropdown-item has-icon text-danger">
                        <i class="fas fa-sign-out-alt"></i> Logout
                    </a>
                </form>

            </div>
        </li>
    </ul>
</nav>
```

And for the sidebar

```bash
php artisan make:view layouts.partials.sidebar
```

Define it as follows

```html
<div class="main-sidebar sidebar-style-2">
    <aside id="sidebar-wrapper">
        <div class="sidebar-brand text-start px-3">
            <a href="{{ route('admin.home.index') }}">Ecommerce</a>
        </div>
        <div class="sidebar-brand sidebar-brand-sm px-2 text-start">
            <a href="{{ route('admin.home.index') }}">Ec</a>
        </div>
        <ul class="sidebar-menu">
            <li class="menu-header">Dashboard</li>
            <li class="nav-item @if (Route::is('admin.home.index')) active @endif">
                <a href="{{ route('admin.home.index') }}"
                   class="nav-link">
                    <i class="fas fa-fire"></i> <span>Dashboard</span>
                </a>
            </li>
        </ul>
    </aside>
</div>
```

Lastly, the footer

```bash
php artisan make:view layouts.partials.footer
```

with the following content

```html
<footer class="main-footer">
    <div class="footer-left">
        Copyright {{ date('Y') }} Ecommerce. All rights reserved.
    </div>
    <div class="footer-right">
        2.3.0
    </div>
</footer>
```

After this, our layout is ready for use.

Looking at our test, we asserted that `viewIs('admin.home.index')`, let's go ahead and create that view.

```bash
php artisan make:view admin.home.index -e layouts.app
```

We also asserted that we will see 'Dashboard' so let's define our simple view like this for now

```html
@extends('layouts.app')

@section('content')
    <section class="section">
        <div class="section-header">
            <h1>Dashboard</h1>
        </div>

        <div class="section-body">
            <h2 class="section-title">Welcome to Ecommerce Dashboard</h2>
            <p class="section-lead">This page is just an example for you to create your own page.</p>
        </div>
    </section>
@endsection
```

Lastly let's tell our `HomeController` to return that view in the index action

```php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class HomeController extends Controller
{
    /**
     * Display the admin home page.
     *
     * @return Renderable
     */
    public function index(): Renderable
    {
        return view('admin.home.index');
    }
}
```

Now let's run our tests

```bash
php artisan test --filter HomeControllerTest::test_can_see_admin_home_page_if_user_is_super_admin
```

And with that all our other will pass as well

```php
php artisan test
```

## Users

Now we need a way for the super admin to create other users and create and assign roles like `admin`, `seo specialist`, `marketer`, etc and give them permissions like `edit products`, etc

Let's starting by generating the UserController

```bash
php artisan make:controller Admin\\UserController -m User --test -R
```

Like the responsible developers we are, let's start by writing a bunch of tests

```php
<?php

namespace Tests\Feature\Http\Controllers\Admin;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

class UserControllerTest extends TestCase
{
    use RefreshDatabase;
    use withFaker;

    /**
     * Test that the admin user index page is accessible to super admins.
     *
     * @return void
     */
    public function test_can_see_admin_user_index_page_if_user_is_super_admin(): void
    {
        $this->actingAs($this->superAdminUser())
            ->get(route('admin.users.index'))
            ->assertStatus(200)
            ->assertViewIs('admin.users.index')
            ->assertSee('Users')
            ->assertViewHas('users');
    }

    /**
     * Test that a user cannot see the admin user index page if they are not a super admin.
     *
     * @return void
     */
    public function test_cannot_see_admin_user_index_page_if_user_is_not_super_admin(): void
    {
        $this->actingAs($this->user())
            ->get(route('admin.users.index'))
            ->assertForbidden();
    }

    /**
     * Test that the user is redirected to the login page if they are not logged in.
     *
     * @return void
     */
    public function test_cannot_see_admin_user_index_page_if_user_is_not_logged_in(): void
    {
        $this->get(route('admin.users.index'))->assertRedirect(route('login'));
    }

    /**
     * Test that the admin user index page is accessible if user has the permission
     *
     * @return void
     */
    public function test_can_see_admin_user_index_page_if_user_has_permission(): void
    {
        $user = $this->user();
        $user->givePermissionTo([
            Permission::findOrCreate('view all users'),
            Permission::findOrCreate('view admin dashboard'),
        ]);

        $this->actingAs($user)
            ->get(route('admin.users.index'))
            ->assertStatus(200)
            ->assertViewIs('admin.users.index')
            ->assertSee('Users');
    }

    /**
     * Test that the admin user create page is accessible to super admins.
     *
     * @return void
     */
    public function test_can_display_create_user_page_if_user_is_super_admin(): void
    {
        $this->actingAs($this->superAdminUser())
            ->get(route('admin.users.create'))
            ->assertStatus(200)
            ->assertViewIs('admin.users.create')
            ->assertSee('Create User');
    }

    /**
     * Test that a user cannot see the admin user create page if they are not a super admin.
     *
     * @return void
     */
    public function test_cannot_display_create_user_page_if_user_is_not_super_admin(): void
    {
        $this->actingAs($this->user())
            ->get(route('admin.users.create'))
            ->assertForbidden();
    }

    /**
     * Test that the user is redirected to the login page if they are not logged in.
     *
     * @return void
     */
    public function test_cannot_display_create_user_page_if_user_is_not_logged_in(): void
    {
        $this->get(route('admin.users.create'))->assertRedirect(route('login'));
    }

    /**
     * Test that the admin user create page is accessible if user has the permission
     *
     * @return void
     */
    public function test_can_display_create_user_page_if_user_has_permission(): void
    {
        $user = $this->user();
        $user->givePermissionTo(
            Permission::findOrCreate('create user'),
            Permission::findOrCreate('view admin dashboard'),
        );

        $this->actingAs($user)
            ->get(route('admin.users.create'))
            ->assertStatus(200)
            ->assertViewIs('admin.users.create')
            ->assertSee('Create User');
    }

    /**
     * Test that the admin user create page is not accessible if user does not have the permission
     *
     * @return void
     */
    public function test_cannot_display_create_user_page_if_user_does_not_have_permission(): void
    {
        $user = $this->user();

        $this->actingAs($user)
            ->get(route('admin.users.create'))
            ->assertForbidden();
    }

    /**
     * Test that the admin user store page is accessible to super admins.
     *
     * @return void
     */
    public function test_can_store_user_if_user_is_super_admin(): void
    {
        $this->actingAs($this->superAdminUser())
            ->post(route('admin.users.store'), [
                'name' => 'Test User',
                'email' => $this->faker()->safeEmail,
                'password' => 'password',
                'password_confirmation' => 'password',
            ])
            ->assertRedirect(route('admin.users.index'))
            ->assertSessionHas('success', 'User created successfully.')
            ->assertSessionMissing('error');

        $this->assertCount(2, User::all());
    }

    /**
     * Test that a user cannot store a user if they are not a super admin.
     *
     * @return void
     */
    public function test_cannot_store_user_if_user_is_not_super_admin(): void
    {
        $this->actingAs($this->user())
            ->post(route('admin.users.store'), [
                'name' => 'Test User',
                'email' => $this->faker()->safeEmail,
                'password' => 'password',
                'password_confirmation' => 'password',
            ])
            ->assertForbidden();
    }

    /**
     * Test that the user is redirected to the login page if they are not logged in.
     *
     * @return void
     */
    public function test_cannot_store_user_if_user_is_not_logged_in(): void
    {
        $this->post(route('admin.users.store'), [
            'name' => 'Test User',
            'email' => $this->faker()->safeEmail,
            'password' => 'password',
            'password_confirmation' => 'password',
        ])->assertRedirect(route('login'));
    }

    /**
     * Test that the admin user store page is accessible if user has the permission
     *
     * @return void
     */
    public function test_can_store_user_if_user_has_permission(): void
    {
        $user = $this->user();
        $user->givePermissionTo(
            Permission::findOrCreate('create user'),
            Permission::findOrCreate('view admin dashboard'),
        );

        $this->actingAs($user)
            ->post(route('admin.users.store'), [
                'name' => 'Test User',
                'email' => $this->faker()->safeEmail,
                'password' => 'password',
                'password_confirmation' => 'password',
            ])
            ->assertRedirect(route('admin.users.index'))
            ->assertSessionHas('success', 'User created successfully.')
            ->assertSessionMissing('error');

        $this->assertCount(2, User::all());
    }

    /**
     * Test that the admin user store page is not accessible if user does not have the permission
     *
     * @return void
     */
    public function test_cannot_store_user_if_user_does_not_have_permission(): void
    {
        $user = $this->user();

        $this->actingAs($user)
            ->post(route('admin.users.store'), [
                'name' => 'Test User',
                'email' => $this->faker()->safeEmail,
                'password' => 'password',
                'password_confirmation' => 'password',
            ])
            ->assertForbidden();
    }

    /**
     * Test that the request is validated when creating a user.
     *
     * @return void
     */
    public function test_can_validate_user_store_request(): void
    {
        $this->actingAs($this->superAdminUser())
            ->post(route('admin.users.store'), [
                'name' => '',
                'email' => '',
                'password' => '',
                'password_confirmation' => '',
            ])
            ->assertSessionHasErrors(['name', 'email', 'password']);
    }

    /**
     * Test that the admin user edit page is accessible to super admins.
     *
     * @return void
     */
    public function test_can_display_edit_user_page_if_user_is_super_admin(): void
    {
        $user = $this->user();

        $this->actingAs($this->superAdminUser())
            ->get(route('admin.users.edit', $user))
            ->assertStatus(200)
            ->assertViewIs('admin.users.edit')
            ->assertSee('Edit User');
    }

    /**
     * Test that a user cannot see the admin user edit page if they are not a super admin.
     *
     * @return void
     */

    public function test_cannot_display_edit_user_page_if_user_is_not_super_admin(): void
    {
        $user = $this->user();

        $this->actingAs($this->user())
            ->get(route('admin.users.edit', $user))
            ->assertForbidden();
    }

    /**
     * Test that the user is redirected to the login page if they are not logged in.
     *
     * @return void
     */
    public function test_cannot_display_edit_user_page_if_user_is_not_logged_in(): void
    {
        $user = $this->user();

        $this->get(route('admin.users.edit', $user))->assertRedirect(
            route('login'),
        );
    }

    /**
     * Test that the admin user edit page is accessible if user has the permission
     *
     * @return void
     */
    public function test_can_display_edit_user_page_if_user_has_permission(): void
    {
        $user = $this->user();
        $user->givePermissionTo(
            Permission::findOrCreate('update user'),
            Permission::findOrCreate('view admin dashboard'),
        );

        $this->actingAs($user)
            ->get(route('admin.users.edit', $user))
            ->assertStatus(200)
            ->assertViewIs('admin.users.edit')
            ->assertSee('Edit User');
    }

    /**
     * Test that the admin user edit page is not accessible if user does not have the permission
     *
     * @return void
     */
    public function test_cannot_display_edit_user_page_if_user_does_not_have_permission(): void
    {
        $user = $this->user();

        $this->actingAs($user)
            ->get(route('admin.users.edit', $user))
            ->assertForbidden();
    }

    /**
     * Test that the admin user update page is accessible to super admins.
     *
     * @return void
     */
    public function test_can_update_user_if_user_is_super_admin(): void
    {
        $user = $this->user();

        $this->actingAs($this->superAdminUser())
            ->put(route('admin.users.update', $user), [
                'name' => 'Test User',
                'email' => $this->faker()->safeEmail,
            ])
            ->assertRedirect(route('admin.users.index'))
            ->assertSessionHas('success', 'User updated successfully.')
            ->assertSessionMissing('error');

        $this->assertEquals('Test User', $user->fresh()->name);
        $this->assertNotEquals($user->email, $user->fresh()->email);
    }

    /**
     * Test that a user cannot update a user if they are not a super admin.
     *
     * @return void
     */
    public function test_cannot_update_user_if_user_is_not_super_admin(): void
    {
        $user = $this->user();

        $this->actingAs($this->user())
            ->put(route('admin.users.update', $user), [
                'name' => 'Test User',
                'email' => $this->faker()->safeEmail,
            ])
            ->assertForbidden();

        $this->assertNotEquals('Test User', $user->fresh()->name);
        $this->assertEquals($user->email, $user->fresh()->email);
    }

    /**
     * Test that the user is redirected to the login page if they are not logged in.
     *
     * @return void
     */
    public function test_cannot_update_user_if_user_is_not_logged_in(): void
    {
        $user = $this->user();

        $this->put(route('admin.users.update', $user), [
            'name' => 'Test User',
            'email' => $this->faker()->safeEmail,
        ])->assertRedirect(route('login'));
    }

    /**
     * Test that the admin user update page is accessible if user has the permission
     *
     * @return void
     */
    public function test_can_update_user_if_user_has_permission(): void
    {
        $user = $this->user();
        $user->givePermissionTo(
            Permission::findOrCreate('update user'),
            Permission::findOrCreate('view admin dashboard'),
        );

        $this->actingAs($user)
            ->put(route('admin.users.update', $user), [
                'name' => 'Test User',
                'email' => $this->faker()->safeEmail,
            ])
            ->assertRedirect(route('admin.users.index'))
            ->assertSessionHas('success', 'User updated successfully.')
            ->assertSessionMissing('error');

        $this->assertEquals('Test User', $user->fresh()->name);
        $this->assertNotEquals($user->email, $user->fresh()->email);
    }

    /**
     * Test that the admin user update page is not accessible if user does not have the permission
     *
     * @return void
     */
    public function test_cannot_update_user_if_user_does_not_have_permission(): void
    {
        $user = $this->user();

        $this->actingAs($user)
            ->put(route('admin.users.update', $user), [
                'name' => 'Test User',
                'email' => $this->faker()->safeEmail,
            ])
            ->assertForbidden();
    }

    /**
     * Test that the admin user delete page is accessible to super admins.
     *
     * @return void
     */
    public function test_can_delete_user_if_user_is_super_admin(): void
    {
        $user = $this->user();

        $this->actingAs($this->superAdminUser())
            ->delete(route('admin.users.destroy', $user))
            ->assertRedirect(route('admin.users.index'))
            ->assertSessionHas('success', 'User deleted successfully.')
            ->assertSessionMissing('error');
    }

    /**
     * Test that a user cannot delete a user if they are not a super admin.
     *
     * @return void
     */
    public function test_cannot_delete_user_if_user_is_not_super_admin(): void
    {
        $user = $this->user();

        $this->actingAs($this->user())
            ->delete(route('admin.users.destroy', $user))
            ->assertForbidden();
    }

    /**
     * Test that the user is redirected to the login page if they are not logged in.
     *
     * @return void
     */
    public function test_cannot_delete_user_if_user_is_not_logged_in(): void
    {
        $user = $this->user();

        $this->delete(route('admin.users.destroy', $user))->assertRedirect(
            route('login'),
        );
    }

    /**
     * Test that the admin user delete page is accessible if user has the permission
     *
     * @return void
     */
    public function test_can_delete_user_if_user_has_permission(): void
    {
        $user = $this->user();
        $user->givePermissionTo(
            Permission::findOrCreate('delete user'),
            Permission::findOrCreate('view admin dashboard'),
        );

        $this->actingAs($user)
            ->delete(route('admin.users.destroy', $user))
            ->assertRedirect(route('admin.users.index'))
            ->assertSessionHas('success', 'User deleted successfully.')
            ->assertSessionMissing('error');
    }

    /**
     * Test that the admin user delete page is not accessible if user does not have the permission
     *
     * @return void
     */
    public function test_cannot_delete_user_if_user_does_not_have_permission(): void
    {
        $user = $this->user();

        $this->actingAs($user)
            ->delete(route('admin.users.destroy', $user))
            ->assertForbidden();
    }
}
```

To pass our first test let's define the routes for the users. In the `admin.` route group add

```php
Route::resource('users', UserController::class);
```

Next let's add the users link to the sidebar. Below the Dashboard link add

```html
<li class="menu-header">Users</li>
<li class="nav-item @if (Route::is('admin.users.*')) active @endif">
	<a href="{{ route('admin.users.index') }}" class="nav-link">
		<i class="fas fa-users"></i> <span>Users</span>
	</a>
</li>
```

Next, we asserted that the `viewIs('admin.users.index')` let's create that view

```bash
php artisan make:view admin.users.index
```

We also asserted that we will see Users and that the view data has `$users`. Let's define our index view as follow

```html
@extends('layouts.app')

@content('title')
	Users
@endcontent

@section('content')
    <section class="section">
        <div class="section-header">
            <h1>Users</h1>
            <div class="section-header-button">
                <a href="{{ route('admin.users.create') }}"
                   class="btn btn-primary">Create New</a>
            </div>
            <div class="section-header-breadcrumb">
                <div class="breadcrumb-item active"><a href="{{ route('admin.home.index') }}">Dashboard</a></div>
                <div class="breadcrumb-item"><a href="{{ route('admin.users.index') }}">Users</a></div>
                <div class="breadcrumb-item">All Users</div>
            </div>
        </div>
        <div class="section-body">
            <h2 class="section-title">Users</h2>
            <p class="section-lead">
                You can manage all users, such as editing, deleting and more.
            </p>

            <div class="row mt-4">
                <div class="col-12">
                    <div class="card">
                        <div class="card-header">
                            <h4>All Users</h4>
                        </div>
                        <div class="card-body">
                            <div class="table-responsive">
                                <table class="table table-striped">
                                    <tr>
                                        <th>Name</th>
                                        <th>Email</th>
                                        <th>Role</th>
                                        <th>Registered At</th>
                                    </tr>
                                    @foreach ($users as $user)
                                        <tr>
                                            <td>
                                                <a href="#">
                                                    <img alt="image"
                                                         src="https://res.cloudinary.com/dwinzyahj/image/upload/v1609855422/rqyxywrhl5vis0dnaenc.png"
                                                         class="rounded-circle"
                                                         width="35"
                                                         data-toggle="title"
                                                         title="">
                                                    <div class="d-inline-block ml-2">{{ $user->name }}</div>
                                                </a>

                                                <div class="table-links"
                                                     data-controller="obliterate"
                                                     data-obliterate-trash-value="1">
                                                    <a href="{{ route('admin.users.edit', $user) }}">Edit</a>
                                                    <div class="bullet"></div>
                                                    @if ($user->id !== auth()->user()->id)
                                                        <a data-action="click->obliterate#handle"
                                                           href="javascrpit:void(0)"
                                                           class="btn btn-link text-danger">Trash</a>
                                                        <form
                                                              method="POST"
                                                              action="{{ route('admin.users.destroy', $user) }}">
                                                            @csrf
                                                            @method('DELETE')
                                                        </form>
                                                    @endif
                                                </div>
                                            </td>

                                            <td>{{ $user->email }}</td>
                                            <td>{{ $user->roles?->first()?->name ?? 'None' }}</td>
                                            <td>{{ $user->created_at->diffForHumans() }}</td>
                                        </tr>
                                    @endforeach
                                </table>
                            </div>

                            <div class="float-right">
                                {{ $users->links('vendor.pagination.bootstrap-5') }}
                            </div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </section>
@endsection
```

And then publish the laravel pagination inorder to use bootstrap 5 pagination views.

Let's tell our UserController to return this view

```php
/**
 * Display a listing of the resource.
 *
 * @return Renderable
 */
public function index(): Renderable
{
    $users = User::paginate(10);

    return view('admin.users.index', [
        'users' => $users,
    ]);
}
```

Before we run our test let's generate the authorization code

```bash
php artisan authorizer:permissions:generate -m User
```

```bash
php artisan authorizer:policies:generate -m User
```

This will generate a `UserPolicy` and the permissions to `create`, `update`, `view` users.

Let's tell the UserController to authorize all actions by adding this constructor

```php
/**
 * Create a new controller instance.
 */
public function __construct()
{
    $this->authorizeResource(User::class, 'user');
}
```

Let's now run our test

```bash
php artisan test --filter UserControllerTest::test_can_see_admin_user_index_page_if_user_is_super_admin
```

Moving on to the next test, let's create the `admin.users.create` view

```bash
php artisan make:view admin.users.create -e layouts.app
```

And populate it with

```html
@extends('layouts.app')

@section('title')
    Create New User
@endsection

@section('content')
    <section class="section">
        <div class="section-header">
            <div class="section-header-back">
                <a href="{{ route('admin.users.index') }}"
                   class="btn btn-icon"><i class="fas fa-arrow-left"></i></a>
            </div>
            <h1>Create User</h1>
            <div class="section-header-breadcrumb">
                <div class="breadcrumb-item active"><a href="{{ route('admin.home.index') }}">Dashboard</a></div>
                <div class="breadcrumb-item"><a href="{{ route('admin.users.index') }}">Users</a></div>
                <div class="breadcrumb-item">Create</div>
            </div>
        </div>
        <div class="section-body">
            <h2 class="section-title">Create User</h2>
            <p class="section-lead">
                You can add new user accounts to the website
            </p>

            <div class="container">
                <div class="row">
                    <div class="col-12 col-md-7 ms-auto">
                        <div class="card">
                            <div class="card-header">
                                <h4>User information</h4>
                            </div>
                            <div class="card-body">
                                <form action="{{ route('admin.users.store') }}"
                                      method="POST">
                                    @csrf

                                    <div class="form-group">
                                        <label for="email"
                                               class="label form-control-label">Name</label>
                                        <input type="text"
                                               name="name"
                                               class="form-control @error('name') is-invalid @enderror"
                                               value="{{ old('name') }}">
                                        @error('name')
                                            <div class="invalid-feedback">
                                                {{ $message }}
                                            </div>
                                        @enderror
                                    </div>

                                    <div class="form-group">
                                        <label for="email"
                                               class="label form-control-label">Email address</label>
                                        <input type="email"
                                               name="email"
                                               class="form-control @error('email') is-invalid @enderror"
                                               value="{{ old('email') }}">
                                        @error('email')
                                            <div class="invalid-feedback">
                                                {{ $message }}
                                            </div>
                                        @enderror
                                    </div>
                                    <div class="row">
                                        <div class="col-md-6 form-group">
                                            <label for="">New password</label>
                                            <input type="password"
                                                   autocomplete="new-password"
                                                   name="password"
                                                   class="form-control @error('password') is-invalid @enderror"">
                                            @error('password')
                                                <div class="invalid-feedback">
                                                    {{ $message }}
                                                </div>
                                            @enderror
                                        </div>
                                        <div class="col-md-6 form-group">
                                            <label for="">Confirm password</label>
                                            <input type="password"
                                                   autocomplete="new-password"
                                                   name="password_confirmation"
                                                   class="form-control @error('password_confirmation') is-invalid @enderror">
                                            @error('password_confirmation')
                                                <div class="invalid-feedback">
                                                    {{ $message }}
                                                </div>
                                            @enderror
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <button type="submit"
                                                class="btn btn-primary">Create user</button>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </section>
@endsection
```

Let's tell our UserController to return this view

```php
/**
 * Show the form for creating a new resource.
 *
 * @return Renderable
 */
public function create(): Renderable
{
    return view('admin.users.create');
}
```

And again let's run our test

```bash
php artisan test --filter UserControllerTest::test_can_display_create_user_page_if_user_is_super_admin
```

Moving on to the next test. Let's define our `StoreUserRequest` to validate the request before storing the user in the database

```php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreUserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize(): bool
    {
        return $this->user()->can('create user');
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8|confirmed',
        ];
    }
}
```

And then let's define our store action

```php
use App\Actions\Fortify\CreateNewUser;

/**
 * Store a newly created resource in storage.
 *
 * @param StoreUserRequest $request
 * @param CreateNewUser $action
 * @return RedirectResponse
 */
public function store(StoreUserRequest $request, CreateNewUser $action): RedirectResponse 
{
    $action->create([
        ...$request->validated(),
        'terms' => 'on',
        'password_confirmation' => $request->password_confirmation,
    ]);

    return redirect()
        ->route('admin.users.index')
        ->with('success', 'User created successfully.');
}
```

This code simple injects Fortifty's `CreateNewUser` action with [Laravel's service container](https://laravel.com/docs/9.x/container) and passes the validated input to the action to create a new user. It then redirects to the index page with a flash message.

And like always, let's run our test

```bash
php artisan test --filter UserControllerTest::test_can_store_user_if_user_is_super_admin
```

And then to then next test. Let's create the user edit view

```bash
php artisan make:view admin.users.edit -e layouts.app
```

Populate the new view with

```html
@extends('layouts.app')

@section('title')
    Editing {{ $user->name }}
@endsection

@section('content')
    <section class="section">
        <div class="section-header">
            <div class="section-header-back">
                <a href="{{ route('admin.users.index') }}"
                   class="btn btn-icon"><i class="fas fa-arrow-left"></i></a>
            </div>
            <h1>{{ $user->name }}</h1>
            <div class="section-header-breadcrumb">
                <div class="breadcrumb-item active"><a href="{{ route('admin.home.index') }}">Dashboard</a></div>
                <div class="breadcrumb-item"><a href="{{ route('admin.users.index') }}">Users</a></div>
                <div class="breadcrumb-item">Edit</div>
            </div>
        </div>
        <div class="section-body">
            <h2 class="section-title">Edit User</h2>
            <p class="section-lead">
                You can edit user account information like passwords, name and emails
            </p>

            <div class="container">
                <div class="row">
                    <div class="col-12 col-md-7 ms-auto">
                        <div class="card">
                            <div class="card-header">
                                <h4>Profile</h4>
                            </div>
                            <div class="card-body">
                                <form action="{{ route('admin.users.update', $user) }}"
                                      method="POST">
                                    @method('PATCH')
                                    @csrf

                                    <div class="form-group">
                                        <label for="email"
                                               class="label form-control-label">Name</label>
                                        <input type="text"
                                               name="name"
                                               class="form-control @error('name') is-invalid @enderror"
                                               value="{{ old('name', $user->name) }}">
                                        @error('name')
                                            <div class="invalid-feedback">
                                                {{ $message }}
                                            </div>
                                        @enderror
                                    </div>

                                    <div class="form-group">
                                        <label for="email"
                                               class="label form-control-label">Email address</label>
                                        <input type="email"
                                               name="email"
                                               class="form-control @error('email') is-invalid @enderror"
                                               value="{{ old('email', $user->email) }}">
                                        @error('email')
                                            <div class="invalid-feedback">
                                                {{ $message }}
                                            </div>
                                        @enderror
                                    </div>

                                    <div class="form-group">
                                        <button type="submit"
                                                class="btn btn-primary">Update</button>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>

            </div>
    </section>
@endsection
```

Let's tell the `UserController` to return this view in the edit action

```php
/**
 * Show the form for editing the specified resource.
 *
 * @param User $user
 * @return Renderable
 */
public function edit(User $user): Renderable
{
    return view('admin.users.edit', [
        'user' => $user,
    ]);
}
```

Then let's run the test

```bash
php artisan test --filter UserControllerTest::test_can_display_edit_user_page_if_user_is_super_admin
```

For the edit action, first, let's define the `UpdateUserRequest`

```php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UpdateUserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize(): bool
    {
        return $this->user()->can('update user', $this->route('user'));
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' =>
                'required|string|email|max:255|unique:users,email,' .
                $this->route('user')->id,
        ];
    }
}
```

And then the update action in the controller

```php
use App\Actions\Fortify\UpdateUserProfileInformation; 

/**
 * Update the specified resource in storage.
 *
 * @param UpdateUserRequest $request
 * @param User $user
 * @param UpdateUserProfileInformation $action
 * @return RedirectResponse
 */
public function update(UpdateUserRequest $request, User $user, UpdateUserProfileInformation $action): RedirectResponse
{
    $action->update($user, $request->validated());

    return redirect()
        ->route('admin.users.index')
        ->with('success', 'User updated successfully.');
}
```

Just like the `store` action, this code takes a request and the `UpdatesProfileInformation` action from Fortify and calls the update method with the validated input. It then redirects to the user's index page with a flash message.

At this point we should be able to create and edit users as well as see a list of all the registered users in our ecommerce application. The only part left in our defined tests is the ability to delete users, which we will implement in the next post along which will have a nice prompt with the help of sweetalert2 and Stimulus.js.

In the next post we will be implementing the ability to delete users, the ability to create roles and assign them a set of permissions, the ability to roles to different users. If you have any questions, reach out to me on Twitter [@ncubegiven\_](https://twitter.com/ncubegiven_).

In the meantime, subscribe to our newsletter below and get notified when the next post in this series is published