In a few upcoming lessons, let's work on the invitation system.
So, I've just registered, and I want to invite new users to my team/tenant. For that, we will create a new menu item on top called Users
where we will manage the invitations and the users who are invited to the team.
For that, we must introduce a concept called "Team Owner" or the person who created the tenant. Again, there are multiple ways how you can do that in the database. We will add an is_owner
boolean column to the tenant_user
pivot table, which will default to false
and set to true
during registration.
So, first, let's add a column titled is_owner
.
php artisan make:migration "add is owner to tenant user table"
database/migrations/xxx_add_is_owner_to_tenant_user_table.php:
Schema::table('tenant_user', function (Blueprint $table) { $table->boolean('is_owner')->default(false);});
app/Models/Tenant.php:
class Tenant extends Model{ // ... public function users(): BelongsToMany { return $this->belongsToMany(User::class); return $this->belongsToMany(User::class)->withPivot('is_owner'); }}
app/Models/User.php:
class User extends Authenticatable{ // ... public function tenants(): BelongsToMany { return $this->belongsToMany(Tenant::class); return $this->belongsToMany(Tenant::class)->withPivot('is_owner'); }}
Next, when we attach a user to a tenant in the registration Controller, we must set is_owner
to true.
app/Http/Controllers/Auth/RegisteredUserController.php:
class RegisteredUserController extends Controller{ // ... public function store(Request $request): RedirectResponse { $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class], 'password' => ['required', 'confirmed', Rules\Password::defaults()], 'subdomain' => ['required', 'alpha:ascii', 'unique:'.Tenant::class], ]); $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), ]); $tenant = Tenant::create([ 'name' => $request->name . ' Team', 'subdomain' => $request->subdomain, ]); $tenant->users()->attach($user); $tenant->users()->attach($user, ['is_owner' => true]); $user->update(['current_tenant_id' => $tenant->id]); event(new Registered($user)); Auth::login($user); $tenantDomain = str_replace('://', '://' . $request->subdomain . '.', config('app.url')); return redirect($tenantDomain . route('dashboard', absolute: false)); }}
After registering in the database, the is_owner
is true.
Now, with this user who is the owner of a tenant, we should see the page to manage tenant users.
php artisan make:controller UserController
app/Http/Controllers/UserController.php:
use Illuminate\Contracts\View\View; class UserController extends Controller{ public function index(): View { return view('users.index'); }}
resources/views/users/index.php:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Users') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 bg-white border-b border-gray-200"> Coming soon. </div> </div> </div> </div></x-app-layout>
routes/web.php:
// ... Route::middleware('auth')->group(function () { Route::get('tenants/change/{tenantId}', \App\Http\Controllers\TenantController::class)->name('tenants.change'); Route::resource('tasks', \App\Http\Controllers\TaskController::class); Route::resource('projects', \App\Http\Controllers\ProjectController::class); Route::resource('users', \App\Http\Controllers\UserController::class)->only('index', 'store'); Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');}); require __DIR__.'/auth.php';
resources/views/layouts/navigation.blade.php:
// ... <!-- Navigation Links --><div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex"> <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')"> {{ __('Dashboard') }} </x-nav-link> <x-nav-link :href="route('tasks.index')" :active="request()->routeIs('tasks.*')"> {{ __('Tasks') }} </x-nav-link> <x-nav-link :href="route('projects.index')" :active="request()->routeIs('projects.*')"> {{ __('Projects') }} </x-nav-link> <x-nav-link :href="route('users.index')" :active="request()->routeIs('users.*')"> {{ __('Users') }} </x-nav-link></div> // ...
In the navigation, we can see the Users
menu item and visit this page.
Finally, we must restrict access to this Users
page. For that, we will define a Gate. In the Gate, we will check if the user of the current tenant is the owner.
app/Providers/AppServiceProviders.php:
use App\Models\User;use Illuminate\Support\Facades\Gate; class AppServiceProvider extends ServiceProvider{ // ... public function boot(): void { Gate::define('manage-users', function (User $user) { return $user->tenants() ->wherePivot('tenant_id', $user->current_tenant_id) ->wherePivot('is_owner', true) ->exists(); }); }}
Next, we must use this Gate on the Route and navigation.
routes/web.php:
// ... Route::middleware('auth')->group(function () { Route::get('tenants/change/{tenantId}', \App\Http\Controllers\TenantController::class)->name('tenants.change'); Route::resource('tasks', \App\Http\Controllers\TaskController::class); Route::resource('projects', \App\Http\Controllers\ProjectController::class); Route::resource('users', \App\Http\Controllers\UserController::class) ->only('index', 'store') ->middleware('can:manage-users'); Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');}); require __DIR__.'/auth.php';
resources/views/layouts/navigation.blade.php:
// ... <!-- Navigation Links --><div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex"> <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')"> {{ __('Dashboard') }} </x-nav-link> <x-nav-link :href="route('tasks.index')" :active="request()->routeIs('tasks.*')"> {{ __('Tasks') }} </x-nav-link> <x-nav-link :href="route('projects.index')" :active="request()->routeIs('projects.*')"> {{ __('Projects') }} </x-nav-link> @can('manage-users') <x-nav-link :href="route('users.index')" :active="request()->routeIs('users.*')"> {{ __('Users') }} </x-nav-link> @endcan </div> // ...
If you tried to access the Users
page with a different user, you would get a "403 This Action is Unauthorized" error message.