Full Code of romegasoftware/Multitenancy for AI

master f2c706255cd1 cached
39 files
71.7 KB
17.8k tokens
140 symbols
1 requests
Download .txt
Repository: romegasoftware/Multitenancy
Branch: master
Commit: f2c706255cd1
Files: 39
Total size: 71.7 KB

Directory structure:
gitextract_nf3oca15/

├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── config/
│   └── multitenancy.php
├── migrations/
│   └── create_tenants_table.php.stub
├── phpunit.xml
├── src/
│   ├── Commands/
│   │   ├── AssignAdminPrivileges.php
│   │   ├── InstallCommand.php
│   │   ├── MigrationMakeCommand.php
│   │   └── stubs/
│   │       └── add_tenancy_to_table.stub
│   ├── Contracts/
│   │   └── Tenant.php
│   ├── Exceptions/
│   │   ├── TenantDoesNotExist.php
│   │   └── UnauthorizedException.php
│   ├── Middleware/
│   │   ├── GuestTenantMiddleware.php
│   │   └── TenantMiddleware.php
│   ├── Models/
│   │   └── Tenant.php
│   ├── Multitenancy.php
│   ├── MultitenancyFacade.php
│   ├── MultitenancyServiceProvider.php
│   └── Traits/
│       ├── BelongsToTenant.php
│       └── HasTenants.php
└── tests/
    ├── Databases/
    │   └── MigrateDatabaseTest.php
    ├── Feature/
    │   ├── BelongsToTenantTest.php
    │   ├── Commands/
    │   │   ├── AssignAdminPrivilegesTest.php
    │   │   ├── InstallCommandTest.php
    │   │   └── MigrationMakeCommandTest.php
    │   ├── GateTest.php
    │   ├── HasTenantTest.php
    │   ├── Middleware/
    │   │   ├── GuestMiddlewareTest.php
    │   │   └── TenantMiddlewareTest.php
    │   ├── MultitenancyTest.php
    │   └── TenantTest.php
    ├── Fixtures/
    │   ├── Controllers/
    │   │   ├── ProductController.php
    │   │   └── UserController.php
    │   ├── Policies/
    │   │   └── ProductPolicy.php
    │   ├── Product.php
    │   └── User.php
    └── TestCase.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store
/vendor
.phpunit.result.cache

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 Romega Digital

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Multitenancy Laravel Package

[![Total Downloads](https://img.shields.io/packagist/dt/romegadigital/multitenancy.svg?style=flat-square)](https://packagist.org/packages/romegadigital/multitenancy)

This package provides a convenient way to add multitenancy to your Laravel application. It manages models and relationships for Tenants, identifies incoming traffic by subdomain, and associates it with a corresponding tenant. Users not linked with a specific subdomain or without a matching tenant in the Tenant table are presented with a 403 error.

**Note:** Any resources saved while accessing a scoped subdomain will automatically be saved against the current tenant, based on subdomain.

**Note:** The `admin` subdomain is reserved for the package to remove all scopes from users with a `Super Administrator` role.

## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Console Commands](#console-commands)
- [Nova Management](#managing-with-nova)
- [Testing](#testing-package)

## Installation

#### 1. Use composer to install the package:

``` bash
composer require romegadigital/multitenancy
```

In Laravel 5.5 and newer, the service provider gets registered automatically. For older versions, add the service provider in the `config/app.php` file:

```php
'providers' => [
    // ...
    RomegaDigital\Multitenancy\MultitenancyServiceProvider::class,
];
```

#### 2. Publish the config file

```bash
php artisan vendor:publish --provider="RomegaDigital\Multitenancy\MultitenancyServiceProvider" --tag="config"
```

#### 3. Run the setup

```bash
php artisan multitenancy:install
```

This command will:
- Publish and migrate required migrations
- Add a `Super Administrator` role and `access admin` permission
- Create an `admin` Tenant model


#### 4. Update your `.env` file
The package needs to know your base URL so it can determine what constitutes a tenant by the subdomain.

Add this to your `.env` file: `MULTITENANCY_BASE_URL=`

#### 5. Update your User model

Apply the `RomegaDigital\Multitenancy\Traits\HasTenants` and `Spatie\Permission\Traits\HasRoles` traits to your User model(s):

```php
use Spatie\Permission\Traits\HasRoles;
use RomegaDigital\Multitenancy\Traits\HasTenants;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use HasTenants, HasRoles;
    // ...
}
```


## Usage


Tenants require a name to identify the tenant and a subdomain that is associated with that user. Example:

`tenant1.example.com`

`tenant2.example.com`


**Note:** You define the base url `example.com` in the `config/multitenancy.php` file.


These Tenants could be added to the database like so:

```php
Tenant::create([
    'name'    => 'An Identifying Name',
    'domain'  => 'tenant1'
]);
Tenant::create([
    'name'    => 'A Second Customer',
    'domain'  => 'tenant2'
]);
```

You can then attach user models to the Tenant:

```php
$user = User::first();
Tenant::first()->users()->save($user);
```

Create Tenants, associate them with Users, and define access rules using provided Middleware. Check [the detailed usage guide](#detailed-usage-guide) for examples.


## Detailed Usage Guide
### 1. **Models and relationships:** 
Use Eloquent to access User's tenants (`User::tenants()->get()`) and Tenant's users (`Tenant::users()->get()`). Add new tenants and their associated users to the database.


### 2. **Middleware:** 
Add `TenantMiddleware` and `GuestTenantMiddleware` to your `app/Http/Kernel.php` file and apply them to routes.

#### Tenant Middleware

```php
protected $middlewareAliases = [
    // ...
    'tenant.auth' => \RomegaDigital\Multitenancy\Middleware\TenantMiddleware::class,
];
```

Then you can bring multitenancy to your routes using middleware rules:

```php
Route::group(['middleware' => ['tenant.auth']], function () {
    // ...
});
```

#### Guest Tenant Middleware

This package comes with `GuestTenantMiddleware` middleware which applies the tenant scope to all models and can be used for allowing guest users to access Tenant related pages. You can add it inside your `app/Http/Kernel.php` file.

```php
protected $middlewareAliases = [
    // ...
    'tenant.guest' => \RomegaDigital\Multitenancy\Middleware\GuestTenantMiddleware::class,
];
```

Then you can bring multitenancy to your routes using middleware rules:

```php
Route::group(['middleware' => ['tenant.guest']], function () {
    // ...
});
```


### 3. **Tenant Assignment for Models:** 
Make models tenant-aware by adding a trait and migration. Then apply tenant scoping automatically. This allows users to access `tenant1.example.com` and return the data from `tenant1` only.

For example, say you wanted Tenants to manage their own `Product`. In your `Product` model, add the `BelongsToTenant` trait. Then run the provided console command to add the necessary relationship column to your existing `products` table.

```php
use Illuminate\Database\Eloquent\Model;
use RomegaDigital\Multitenancy\Traits\BelongsToTenant;

class Product extends Model
{
    use BelongsToTenant;

    // ...
}
```
**Add tenancy to a model's table:** `php artisan multitenancy:migration products`

### 4. **Access to Current Tenant:** 
Use `app('multitenancy')->currentTenant()` to get the current tenant model.

### 5. **Admin Domain Access:** 
Assign the `Super Administrator` role to a user to enable access to the `admin` subdomain. Manually create an admin portal if necessary.

### 6. **Auto-assign Users to Tenants:** 
Enable `ignore_tenant_on_user_creation` setting to automatically assign users to the Tenant subdomain on which they are created.

### 7. **Give a user `Super Administration` rights:**

In order to access the `admin.example.com` subdomain, a user will need the `access admin` permission. This package relies on [Spatie's Laravel Permission](https://github.com/spatie/laravel-permission) package and is automatically included as a dependency when installing this package. We also provide a `Super Administrator` role on migration that has the relevant permission already associated with it. Assign the `Super Administrator` role to an admin user to provide the access they need. See the [Laravel Permission](https://github.com/spatie/laravel-permission) documentation for more on adding users to the appropriate role and permission.

The Super Administrator is a special user role with privileged access. Users with this role can access all model resources, navigate across different tenants' domains, and gain entry to the `admin` subdomain where all tenant scopes are disabled.

When a user is granted the `Super Administrator` role, they can freely access the `admin` subdomain. In this context, tenant scopes aren't applied. This privilege allows Super Administrators to manage data across all instances without requiring specific access to each individual tenant's account.

Give a user `Super Administration` rights: `php artisan multitenancy:super-admin admin@example.com`

## Managing with Nova

You can manage the resources of this package in Nova with the [MultitenancyNovaTool](https://github.com/romegadigital/MultitenancyNovaTool).

## Testing Package

Run tests with the command:

`php vendor/bin/testbench package:test`


================================================
FILE: composer.json
================================================
{
    "name": "romegadigital/multitenancy",
    "description": "Adds domain based multitenancy to Laravel applications.",
    "license": "MIT",
    "authors": [
        {
            "name": "Braden Keith",
            "email": "bkeith@romegadigital.com"
        }
    ],
    "autoload": {
        "psr-4": {
            "RomegaDigital\\Multitenancy\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "RomegaDigital\\Multitenancy\\Tests\\": "tests/"
        }
    },
    "require": {
        "spatie/laravel-permission": "^6.9.0"
    },
    "extra": {
        "laravel": {
            "providers": [
                "RomegaDigital\\Multitenancy\\MultitenancyServiceProvider"
            ]
        }
    },
    "require-dev": {
        "orchestra/testbench": "^7",
        "nunomaduro/collision": "^6.0"
    }
}


================================================
FILE: config/multitenancy.php
================================================
<?php

return [
    /*
    |--------------------------------------------------------------------------
    | User Model
    |--------------------------------------------------------------------------
    |
    | This is the model you are using for Users that will be attached to the
    | Tenant instance. Users must be attached to domains in order to have
    | access to tenant instance.
    */

    'user_model' => \App\Models\User::class,

    /*
    |--------------------------------------------------------------------------
    | Base URL
    |--------------------------------------------------------------------------
    |
    | This is the URL you would like to serve as the base of your app. It
    | should not contain a scheme (ie: http://, https://).
    | By default, it will attempt to use the host name with the TLD and domain
    | name stripped.
    |
    | Default: null
    */

    'base_url' => env('MULTITENANCY_BASE_URL', null),

    /*
    |--------------------------------------------------------------------------
    | Roles
    |--------------------------------------------------------------------------
    |
    | The values (on the right) determine how the roles with
    | keys (on the left) are being named in the database.
    */

    'roles' => [
        // What the Super Administrator is called in your app
        'super_admin' => 'Super Administrator',
    ],

    /*
    |--------------------------------------------------------------------------
    | Policy Files
    |--------------------------------------------------------------------------
    |
    | The policy file to use when using [MultitenancyNovaTool]
    | (https://github.com/romegasoftware/MultitenancyNovaTool)
    */

    'policies' => [
        'role' => \RomegaDigital\MultitenancyNovaTool\Policies\RolePolicy::class,
        'permission' => \RomegaDigital\MultitenancyNovaTool\Policies\PermissionPolicy::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Nova Resource Files
    |--------------------------------------------------------------------------
    |
    | The Nova resources to use when using [MultitenancyNovaTool]
    | (https://github.com/romegasoftware/MultitenancyNovaTool)
    */

    'resources' => [
        'role' => \Vyuldashev\NovaPermission\Role::class,
        'permission' => \Vyuldashev\NovaPermission\Permission::class,
    ],

    /*
    |--------------------------------------------------------------------------
    | Tenant Model
    |--------------------------------------------------------------------------
    |
    | This is the model you are using for Tenants that will be attached to the
    | User instance. It would be recommended to extend the Tenant model as
    | defined in the package, but if you replace it, be sure to implement
    | the RomegaDigital\Multitenancy\Contracts\Tenant contract.
    */

    'tenant_model' => \RomegaDigital\Multitenancy\Models\Tenant::class,

    'table_names' => [
        /**
         * We need to know which table to setup foreign relationships on.
         */

        'users' => 'users',

        /**
         * If overwriting `tenant_model`, you may also wish to define a new table
         */

        'tenants' => 'tenants',

        /**
         * Define the relationship table for the belongsToMany relationship
         */

        'tenant_user' => 'tenant_user',
    ],

    /*
    |--------------------------------------------------------------------------
    | Redirect Route
    |--------------------------------------------------------------------------
    |
    | This is the name of the route users who aren't logged in will be redirected to
    */

    'redirect_route' => 'login',

    /*
    |--------------------------------------------------------------------------
    | Ignore Tenant on User creation
    |--------------------------------------------------------------------------
    |
    | By default a user is assigned the tenant it is created on. If you create
    | a user while being on the `admin` tenant, this would assign the created
    | user the `admin` tenant automatically. If you don't want to get tenants
    | assigned to users automatically simply disable this setting by setting
    | it to false.
    */

    'ignore_tenant_on_user_creation' => false,
];


================================================
FILE: migrations/create_tenants_table.php.stub
================================================
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;

class CreateTenantsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableNames = config('multitenancy.table_names');

        Schema::create($tableNames['tenants'], function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name')->unique();
            $table->string('domain')->unique();
            $table->softDeletes();
            $table->timestamps();
        });

        Schema::create($tableNames['tenant_user'], function (Blueprint $table) use ($tableNames) {
            $table->bigIncrements('id');

            $table->unsignedBigInteger('tenant_id');
            $table->foreign(Str::singular($tableNames['tenants']).'_id')
                ->references('id')
                ->on($tableNames['tenants'])
                ->onDelete('cascade');

            $table->unsignedBigInteger('user_id');
            $table->foreign(Str::singular($tableNames['users']).'_id')
                ->references('id')
                ->on($tableNames['users'])
                ->onDelete('cascade');

            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        $tableNames = config('multitenancy.table_names');

        Schema::table($tableNames['tenant_user'], function (Blueprint $table) use ($tableNames) {
            $table->dropForeign([Str::singular($tableNames['tenants']).'_id']);
            $table->dropForeign([Str::singular($tableNames['users']).'_id']);
        });

        Schema::dropIfExists($tableNames['tenants']);
        Schema::dropIfExists($tableNames['tenant_user']);
    }
}


================================================
FILE: phpunit.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         verbose="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Multitenancy Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">src/</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="BCRYPT_ROUNDS" value="4"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
    </php>
</phpunit>


================================================
FILE: src/Commands/AssignAdminPrivileges.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Commands;

use Illuminate\Console\Command;
use Spatie\Permission\Models\Role;
use RomegaDigital\Multitenancy\Multitenancy;
use Spatie\Permission\Exceptions\RoleDoesNotExist;
use RomegaDigital\Multitenancy\Exceptions\TenantDoesNotExist;

class AssignAdminPrivileges extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'multitenancy:super-admin
                                {identifier : Unique property identifying the user}
                                {--M|model=\App\Models\User : Model to query the user}
                                {--C|column=email : Property column name}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Give a user super-admin rights and add him to the `admin` tenant';

    /**
     * Multitenancy Service Class.
     *
     * @var RomegaDigital\Multitenancy\Multitenancy
     */
    protected $multitenancy;

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct(Multitenancy $multitenancy)
    {
        parent::__construct();

        $this->multitenancy = $multitenancy;
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $column = $this->option('column');
        $userModel = $this->option('model');
        $identifier = $this->argument('identifier');

        if (!class_exists($userModel)) {
            return $this->error('User model ' . $userModel . ' can not be found!');
        }

        if (!$user = $this->getUser($userModel, $column, $identifier)) {
            return 0;
        }

        if (!$adminRole = $this->getAdminRole()) {
            return 0;
        }

        if (!$adminTenant = $this->getAdminTenant()) {
            return 0;
        }

        $user->assignRole($adminRole);
        $user->tenants()->save($adminTenant);

        $this->info('User with ' . $column . ' ' . $user->{$column} . ' granted Super-Administration rights.');

        return 1;
    }

    /**
     * Get user model data.
     *
     * @param string $userModel
     * @param string $column
     * @param string $identifier
     *
     * @return Illuminate\Database\Eloquent\Model
     */
    protected function getUser($userModel, $column, $identifier)
    {
        if (!$user = $userModel::where($column, $identifier)->first()) {
            return $this->modelNotFound('User', $column, $identifier);
        }

        return $user;
    }

    /**
     * Get admin role.
     *
     * @return Spatie\Permission\Contracts\Role
     */
    protected function getAdminRole()
    {
        try {
            return Role::findByName(config('multitenancy.roles.super_admin'));
        } catch (RoleDoesNotExist $exception) {
            return $this->cancel('Role', 'name', config('multitenancy.roles.super_admin'));
        }
    }

    /**
     * Get admin tenant.
     *
     * @return RomegaDigital\Multitenancy\Contracts\Tenant
     */
    protected function getAdminTenant()
    {
        try {
            return $this->multitenancy->getTenantClass()::findByDomain('admin');
        } catch (TenantDoesNotExist $exception) {
            return $this->cancel('Tenant', 'domain', 'admin');
        }
    }

    /**
     * Cancel the command due to errors.
     *
     * @param Illuminate\Database\Eloquent\Model $model
     * @param string                             $column
     * @param string                             $identifier
     *
     * @return bool
     */
    protected function cancel($model, $column, $identifier)
    {
        $this->modelNotFound($model, $column, $identifier);
        $this->line('');
        $this->alert('Did you already run `multitenancy:install` command?');

        return false;
    }

    /**
     * Write an error for a model which can not be found.
     *
     * @param Illuminate\Database\Eloquent\Model $model
     * @param string                             $column
     * @param string                             $identifier
     *
     * @return void
     */
    protected function modelNotFound($model, $column, $identifier)
    {
        $this->error("$model with $column `$identifier` can not be found!");
    }
}


================================================
FILE: src/Commands/InstallCommand.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Commands;

use Illuminate\Console\Command;
use RomegaDigital\Multitenancy\Multitenancy;

class InstallCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'multitenancy:install
                                {--M|migrations= : Run migrations}
                                {--R|roles= : Add super admin role}
                                {--T|tenant= : Add admin tenant}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Perform all steps necessary to setup package quickly';

    /**
     * Multitenancy Service Class.
     *
     * @var RomegaDigital\Multitenancy\Multitenancy
     */
    protected $multitenancy;

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct(Multitenancy $multitenancy)
    {
        parent::__construct();

        $this->multitenancy = $multitenancy;
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $this->option('migrations') ?? $this->handleMigrations();
        $this->option('roles') ?? $this->addSuperAdminRole();
        $this->option('tenant') ?? $this->addAdminTenant();

        return 1;
    }

    /**
     * Publishes and migrates required migrations.
     *
     * @return void
     */
    protected function handleMigrations()
    {
        $this->info('Publishing required migrations...');

        $this->callSilent('vendor:publish', [
            '--provider' => 'Spatie\Permission\PermissionServiceProvider',
            '--tag'      => ['permission-migrations'],
        ]);

        $this->callSilent('vendor:publish', [
            '--provider' => 'RomegaDigital\Multitenancy\MultitenancyServiceProvider',
            '--tag'      => ['migrations'],
        ]);

        $this->info('Migrations published!');

        $this->line('');
        $this->call('migrate');
        $this->line('');
    }

    /**
     * Creates a super admin role and 'access admin'
     * permission.
     *
     * @return void
     */
    protected function addSuperAdminRole()
    {
        $this->info('Adding `Super Administrator` Role...');

        $this->call('permission:create-role', [
            'name'        => config('multitenancy.roles.super_admin'),
            'permissions' => 'access admin',
        ]);

        $this->line('');
    }

    /**
     * Creates the admin tenant model.
     *
     * @return void
     */
    protected function addAdminTenant()
    {
        $this->info('Adding `admin` domain...');

        $this->multitenancy->getTenantClass()::updateOrCreate([
            'name'   => 'Admin Portal',
            'domain' => 'admin',
        ]);

        $this->info('Admin domain added successfully!');
    }
}


================================================
FILE: src/Commands/MigrationMakeCommand.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Commands;

use Illuminate\Console\GeneratorCommand;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Composer;
use Illuminate\Support\Str;

class MigrationMakeCommand extends GeneratorCommand
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'multitenancy:migration {name : The name of the table}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new tenant migration file';

    /**
     * The type of class being generated.
     *
     * @var string
     */
    protected $type = 'Multitenancy migration';

    /**
     * The Composer instance.
     *
     * @var \Illuminate\Support\Composer
     */
    protected $composer;

    /**
     * Create a new command instance.
     *
     * @param \Illuminate\Filesystem\Filesystem $files
     * @param \Illuminate\Support\Composer      $composer
     *
     * @return void
     */
    public function __construct(Filesystem $files, Composer $composer)
    {
        parent::__construct($files);

        $this->composer = $composer;
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        parent::handle();

        $this->composer->dumpAutoloads();

        $this->info('Multitenancy migration created successfully.');

        return 1;
    }

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return __DIR__.'/stubs/add_tenancy_to_table.stub';
    }

    /**
     * Build the class with the given name.
     *
     * @param string $name
     *
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
     *
     * @return string
     */
    protected function buildClass($name)
    {
        $stub = parent::buildClass($name);

        return str_replace(
            ['DummyTable', 'DummyTenantTable'],
            [lcfirst($this->getNameInput()), config('multitenancy.table_names.tenants')],
            $stub
        );
    }

    /**
     * Replace the class name for the given stub.
     *
     * @param string $stub
     * @param string $name
     *
     * @return string
     */
    protected function replaceClass($stub, $name)
    {
        $class = 'AddTenantIDColumnTo'.Str::studly($this->getNameInput()).'Table';

        return str_replace('DummyClass', $class, $stub);
    }

    /**
     * Get the destination class path.
     *
     * @param string $name
     *
     * @return string
     */
    protected function getPath($name)
    {
        $timestamp = date('Y_m_d_His');
        $table = lcfirst($this->getNameInput());

        return $this->laravel->databasePath()."/migrations/{$timestamp}_add_tenant_id_column_to_{$table}_table.php";
    }
}


================================================
FILE: src/Commands/stubs/add_tenancy_to_table.stub
================================================
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class DummyClass extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table("DummyTable", function (Blueprint $table) {
            $table->unsignedBigInteger('tenant_id')->nullable();
            $table->foreign('tenant_id')
                ->references('id')
                ->on("DummyTenantTable")
                ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table("DummyTable", function (Blueprint $table) {
            $table->dropColumn('tenant_id');
        });
    }
}


================================================
FILE: src/Contracts/Tenant.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Contracts;

use Illuminate\Database\Eloquent\Relations\BelongsToMany;

interface Tenant
{
    /**
     * A Tenant belongs to many users.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function users(): BelongsToMany;

    /**
     * Find a Tenant by its domain.
     *
     * @param string $domain
     *
     * @throws \RomegaDigital\Multitenancy\Exceptions\TenantDoesNotExist
     *
     * @return Tenant
     */
    public static function findByDomain(string $domain): self;
}


================================================
FILE: src/Exceptions/TenantDoesNotExist.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Exceptions;

use InvalidArgumentException;

class TenantDoesNotExist extends InvalidArgumentException
{
    /**
     * A Tenant does not exist at the supplied domain.
     *
     * @param string $domain
     *
     * @return static
     */
    public static function forDomain(string $domain): self
    {
        return new static("There is no tenant at domain `{$domain}`.");
    }
}


================================================
FILE: src/Exceptions/UnauthorizedException.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Exceptions;

use Symfony\Component\HttpKernel\Exception\HttpException;

class UnauthorizedException extends HttpException
{
    /**
     * A user does not have valid permissions to access
     * the supplied domain.
     *
     * @param string $domain
     *
     * @return static
     */
    public static function forDomain(string $domain): self
    {
        $message = "The authenticated user does not have access to domain `{$domain}`.";

        $exception = new static(403, $message, null, []);

        return $exception;
    }

    /**
     * A user is not logged in.
     *
     * @return static
     */
    public static function notLoggedIn(): self
    {
        return new static(403, 'User is not logged in.', null, []);
    }
}


================================================
FILE: src/Middleware/GuestTenantMiddleware.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Middleware;

use Closure;
use RomegaDigital\Multitenancy\Multitenancy;

class GuestTenantMiddleware
{
    /**
     * @var RomegaDigital\Multitenancy\Multitenancy
     */
    protected $multitenancy;

    /**
     * Create new TenantMiddleware instance.
     *
     * @param Illuminate\Contracts\Auth\Factory $auth
     * @param RomegaDigital\Multitenancy\Multitenancy $multitenancy
     */
    public function __construct(Multitenancy $multitenancy)
    {
        $this->multitenancy = $multitenancy;
    }

    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure                 $next
     * @param string|null              $guard
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $tenant = $this->multitenancy->receiveTenantFromRequest();

        $this->multitenancy->setTenant($tenant)->applyTenantScopeToDeferredModels();

        return $next($request);
    }
}


================================================
FILE: src/Middleware/TenantMiddleware.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Middleware;

use Closure;
use RomegaDigital\Multitenancy\Multitenancy;
use Illuminate\Contracts\Auth\Factory as Auth;
use RomegaDigital\Multitenancy\Contracts\Tenant;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use RomegaDigital\Multitenancy\Exceptions\UnauthorizedException;

class TenantMiddleware extends Middleware
{
    /**
     * @var RomegaDigital\Multitenancy\Multitenancy
     */
    protected $multitenancy;

    /**
     * Create new TenantMiddleware instance.
     *
     * @param Illuminate\Contracts\Auth\Factory       $auth
     * @param RomegaDigital\Multitenancy\Multitenancy $multitenancy
     */
    public function __construct(Auth $auth, Multitenancy $multitenancy)
    {
        parent::__construct($auth);

        $this->multitenancy = $multitenancy;
    }

    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return string
     */
    protected function redirectTo($request)
    {
        if (! $request->expectsJson()) {
            return route(config('multitenancy.redirect_route'));
        }
    }

    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure                 $next
     * @param string[]                 ...$guards
     *
     * @throws \RomegaDigital\Multitenancy\Exceptions\UnauthorizedException|\Illuminate\Auth\AuthenticationException
     *
     * @return mixed
     */
    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($request, $guards);

        $tenant = $this->multitenancy->receiveTenantFromRequest();

        if (! $this->authorizedToAccessTenant($tenant)) {
            throw UnauthorizedException::forDomain($tenant->domain);
        }

        $this->multitenancy->setTenant($tenant)->applyTenantScopeToDeferredModels();

        $request->merge([Multitenancy::TENANT_SET_HEADER => true]);

        return $next($request);
    }

    /**
     * Check if user is authorized to access tenant's domain.
     *
     * @param \RomegaDigital\Multitenancy\Contracts\Tenant $tenant
     *
     * @return bool
     */
    protected function authorizedToAccessTenant(Tenant $tenant)
    {
        return $tenant && $tenant->users->contains(auth()->user()->id);
    }
}


================================================
FILE: src/Models/Tenant.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use RomegaDigital\Multitenancy\Exceptions\TenantDoesNotExist;
use RomegaDigital\Multitenancy\Contracts\Tenant as TenantContract;

class Tenant extends Model implements TenantContract
{
    use SoftDeletes;
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'domain',
    ];

    /**
     * Create new Tenant instance.
     *
     * @param array $attributes
     */
    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);

        $this->setTable(config('multitenancy.table_names.tenants'));
    }

    /**
     * A Tenant belongs to many users.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(config('multitenancy.user_model'))
            ->withTimestamps();
    }

    /**
     * Find a Tenant by its domain.
     *
     * @param string $domain
     *
     * @throws \RomegaDigital\Multitenancy\Exceptions\TenantDoesNotExist
     *
     * @return \RomegaDigital\Multitenancy\Contracts\Tenant
     */
    public static function findByDomain(string $domain): TenantContract
    {
        $tenant = static::where(['domain' => $domain])->first();

        if (! $tenant) {
            throw TenantDoesNotExist::forDomain($domain);
        }

        return $tenant;
    }
}


================================================
FILE: src/Multitenancy.php
================================================
<?php

namespace RomegaDigital\Multitenancy;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use RomegaDigital\Multitenancy\Contracts\Tenant;

class Multitenancy
{
    const TENANT_SET_HEADER = 'X-Multitenancy-Tenant';

    /**
     * The tenant model as defined in the config file.
     *
     * @var string
     */
    protected $tenantClass;

    /**
     * The current tenant model.
     *
     * @var RomegaDigital\Multitenancy\Contracts\Tenant
     */
    protected $tenant = null;

    /**
     * Models that need scopes before the app fully boots
     * they will be processed at a later time.
     *
     * @var collect
     */
    protected $deferredModels;

    /**
     * Multitenancy constructor.
     */
    public function __construct()
    {
        $this->tenantClass = config('multitenancy.tenant_model');
        $this->deferredModels = collect();
    }

    /**
     * Sets the Tenant to a Tenant Model.
     *
     * @param RomegaDigital\Multitenancy\Contracts\Tenant $tenant
     *
     * @return $this
     */
    public function setTenant(Tenant $tenant)
    {
        $this->tenant = $tenant;

        return $this;
    }

    /**
     * Returns the current Tenant.
     *
     * @return \RomegaDigital\Multitenancy\Contracts\Tenant
     */
    public function currentTenant(): Tenant
    {
        return $this->tenant ?? $this->receiveTenantFromRequest();
    }

    /**
     * Applies applicable tenant scopes to model or if not booted yet
     * store for deferment.
     *
     * @param Illuminate\Database\Eloquent\Model $model
     *
     * @return void|null
     */
    public function applyTenantScope(Model $model)
    {
        if (is_null($this->tenant)) {
            $this->deferredModels->push($model);

            return;
        }

        if ('admin' === $this->tenant->domain) {
            return;
        }

        $model->addGlobalScope('tenant', function (Builder $builder) use ($model) {
            $builder->where($model->qualifyColumn('tenant_id'), '=', $this->tenant->id);
        });
    }

    /**
     * Applies applicable tenant id to model on create.
     *
     * @param Illuminate\Database\Eloquent\Model $model
     *
     * @return void|null
     */
    public function newModel(Model $model)
    {
        if (is_null($this->tenant)) {
            $this->deferredModels->push($model);

            return;
        }

        if (! isset($model->tenant_id)) {
            $model->setAttribute('tenant_id', $this->tenant->id);
        }
    }

    /**
     * Applies applicable tenant scope to deferred model booted
     * before tenants setup.
     */
    public function applyTenantScopeToDeferredModels()
    {
        $this->deferredModels->each(function ($model) {
            $this->applyTenantScope($model);
        });

        $this->deferredModels = collect();
    }

    /**
     * Get an instance of the tenant class.
     *
     * @return \RomegaDigital\Multitenancy\Contracts\Tenant
     */
    public function getTenantClass(): Tenant
    {
        return app($this->tenantClass);
    }

    /**
     * Determines how best to process the URL based
     * on config and then returns the appropriate
     * subdomain text.
     *
     * @return string
     */
    public function getCurrentSubDomain(): string
    {
        $baseURL = config('multitenancy.base_url');

        if (null != $baseURL) {
            return $this->getSubDomainBasedOnBaseURL($baseURL);
        } else {
            return $this->getSubDomainBasedOnHTTPHost();
        }
    }

    /**
     * Parses the request to pull out the first element separated
     * by `.` in the $_SERVER['HTTP_HOST'].
     *
     * ex:
     * test.domain.com returns test
     * test2.test.domain.com returns test2
     *
     * @return string
     */
    protected function getSubDomainBasedOnHTTPHost(): string
    {
        $currentDomain = app('request')->getHost();

        // Get rid of the TLD and root domain
        // ex: masterdomain.test.example.com returns
        // [ masterdomain, test ]
        $subdomains = explode('.', $currentDomain, -2);

        // Combine multiple level of domains into 1 string
        // ex: back to masterdomain.test
        $subdomain = implode('.', $subdomains);

        return $subdomain;
    }

    /**
     * Parses the request and removes the portion of the URL
     * that matches the Base URL as defined in the config file.
     *
     * ex:
     * baseURL = app.domain.com
     * test2.app.domain.com returns test2
     *
     * @return string
     */
    protected function getSubDomainBasedOnBaseURL(string $baseURL): string
    {
        $currentDomain = app('request')->getHost();

        //Remove the base domain from the currentDomain string
        $subdomain = str_replace($baseURL, '', $currentDomain);

        // If the last element is a period, remove it
        // Necessary to run this check, incase we're
        // processing the base domain.
        if ('.' == substr($subdomain, -1)) {
            $subdomain = substr($subdomain, 0, -1);
        }

        return $subdomain;
    }

    /**
     * Returns tenant from request subdomain.
     *
     * @return \RomegaDigital\Multitenancy\Contracts\Tenant
     */
    public function receiveTenantFromRequest()
    {
        $domain = $this->getCurrentSubDomain();

        return $this->getTenantClass()::findByDomain($domain);
    }
}


================================================
FILE: src/MultitenancyFacade.php
================================================
<?php

namespace RomegaDigital\Multitenancy;

use Illuminate\Support\Facades\Facade;

class MultitenancyFacade extends Facade
{
    /**
     * Access the facade.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'multitenancy';
    }
}


================================================
FILE: src/MultitenancyServiceProvider.php
================================================
<?php

namespace RomegaDigital\Multitenancy;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\ServiceProvider;
use RomegaDigital\Multitenancy\Commands\InstallCommand;
use RomegaDigital\Multitenancy\Commands\MigrationMakeCommand;
use RomegaDigital\Multitenancy\Commands\AssignAdminPrivileges;
use RomegaDigital\Multitenancy\Contracts\Tenant as TenantContract;

class MultitenancyServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the package services.
     *
     * @param Illuminate\Filesystem\Filesystem $filesystem
     */
    public function boot(Filesystem $filesystem)
    {
        $this->loadMigrationsFrom(realpath(__DIR__ . '/../migrations'));

        if ($this->app->runningInConsole()) {
            $this->registerPublishing($filesystem);
        }

        $this->registerCommands();
        $this->registerModelBindings();

        Gate::before(function ($user, $ability) {
            if ($user->hasRole(config('multitenancy.roles.super_admin'))
                && 'admin' === app('multitenancy')->getCurrentSubDomain()) {
                return true;
            }
        });
    }

    /**
     * Register the application services.
     */
    public function register()
    {
        $this->mergeConfigFrom(
            __DIR__ . '/../config/multitenancy.php',
            'multitenancy'
        );

        $this->app->singleton(Multitenancy::class, function () {
            return new Multitenancy();
        });

        $this->app->alias(Multitenancy::class, 'multitenancy');
    }

    /**
     * Register the package's publishable resources.
     *
     * @param Illuminate\Filesystem\Filesystem $filesystem
     */
    protected function registerPublishing(Filesystem $filesystem)
    {
        $this->publishes([
            __DIR__ . '/../migrations/create_'.config('multitenancy.table_names.tenants').'_table.php.stub' => $this->getMigrationFileName($filesystem),
        ], 'migrations');

        $this->publishes([
            __DIR__ . '/../config/multitenancy.php' => config_path('multitenancy.php'),
        ], 'config');
    }

    /**
     * Registers all commands within the package.
     */
    protected function registerCommands()
    {
        $this->commands([
            InstallCommand::class,
            MigrationMakeCommand::class,
            AssignAdminPrivileges::class,
        ]);
    }

    /**
     * Register model bindings.
     */
    protected function registerModelBindings()
    {
        $this->app->bind(TenantContract::class, $this->app->config['multitenancy.tenant_model']);
    }

    /**
     * Returns existing migration file if found, else uses the current timestamp.
     *
     * @param Illuminate\Filesystem\Filesystem $filesystem
     *
     * @return string
     */
    protected function getMigrationFileName(Filesystem $filesystem): string
    {
        $timestamp = date('Y_m_d_His');

        return Collection::make($this->app->databasePath() . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR)
            ->flatMap(function ($path) use ($filesystem) {
                return $filesystem->glob($path . '*_create_tenants_table.php');
            })->push($this->app->databasePath() . "/migrations/{$timestamp}_create_tenants_table.php")
            ->first();
    }
}


================================================
FILE: src/Traits/BelongsToTenant.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Traits;

use RomegaDigital\Multitenancy\Multitenancy;

trait BelongsToTenant
{
    /**
     * The "booting" method of the tenant model.
     * This defines the query scopes and creation scopes.
     */
    public static function bootBelongsToTenant()
    {
        resolve(Multitenancy::class)->applyTenantScope(new static());

        static::creating(function ($model) {
            resolve(Multitenancy::class)->newModel($model);
        });
    }

    /**
     * The model belongs to a tenant.
     *
     * @return Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function tenant()
    {
        return $this->belongsTo(config('multitenancy.tenant_model'));
    }
}


================================================
FILE: src/Traits/HasTenants.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Traits;

use RomegaDigital\Multitenancy\Multitenancy;

trait HasTenants
{
    /**
     * Add the current tenant to the created user tenants.
     */
    public static function bootHasTenants()
    {
        static::created(function ($model) {
            $ignoreTenantOnUserCreation = config('multitenancy.ignore_tenant_on_user_creation');

            if (! request()->has(Multitenancy::TENANT_SET_HEADER) || $ignoreTenantOnUserCreation) {
                return;
            }

            $model->tenants()->save(
                resolve(Multitenancy::class)->currentTenant()
            );
        });
    }

    /**
     * The model belongs to many tenants.
     *
     * @return Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function tenants()
    {
        return $this->belongsToMany(config('multitenancy.tenant_model'))
            ->withTimestamps();
    }
}


================================================
FILE: tests/Databases/MigrateDatabaseTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Databases;

use RomegaDigital\Multitenancy\Tests\TestCase;

class MigrateDatabaseTest extends TestCase
{
    /** @test */
    public function it_runs_the_migrations()
    {
        $columns = \Schema::getColumnListing('tenants');
        $this->assertEquals([
            'id',
            'name',
            'domain',
            'deleted_at',
            'created_at',
            'updated_at',
        ], $columns);

        $columns = \Schema::getColumnListing('tenant_user');
        $this->assertEquals([
            'id',
            'tenant_id',
            'user_id',
            'created_at',
            'updated_at',
            'deleted_at',
        ], $columns);
    }
}


================================================
FILE: tests/Feature/BelongsToTenantTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature;

use RomegaDigital\Multitenancy\Models\Tenant;
use RomegaDigital\Multitenancy\Tests\TestCase;
use RomegaDigital\Multitenancy\Tests\Fixtures\Product;
use RomegaDigital\Multitenancy\Tests\Fixtures\Controllers\ProductController;

class BelongsToTenantTest extends TestCase
{
    /**
     * Define environment setup.
     *
     * @param Illuminate\Foundation\Application $app
     */
    protected function getEnvironmentSetUp($app)
    {
        parent::getEnvironmentSetUp($app);

        $app['router']->resource('products', ProductController::class);
    }

    /**
     * Turn the given URI into a fully qualified URL.
     *
     * @param string $uri
     *
     * @return string
     */
    protected function prepareUrlForRequest($uri)
    {
        $uri = "http://{$this->testTenant->domain}.localhost.com/{$uri}";

        return trim($uri, '/');
    }

    /** @test */
    public function it_adds_current_tenant_id_to_model_on_create()
    {
        $this->actingAs($this->testUser);
        $this->testTenant->users()->save($this->testUser);

        $response = $this->post('products', [
            'name' => 'Another Tenants Product',
        ]);
        $response->assertStatus(201);
        $this->assertEquals(Product::first()->tenant_id, $this->testTenant->id);
    }

    /** @test */
    public function it_only_retrieves_records_scoped_to_current_subdomain()
    {
        $this->actingAs($this->testUser);
        $this->testTenant->users()->save($this->testUser);

        Product::create([
            'name' => 'Another Tenants Product',
            'tenant_id' => Tenant::create([
                'name' => 'Another Tenant',
                'domain' => 'anotherdomain',
            ])->id,
        ]);

        $response = $this->get('products');
        $response->assertStatus(200);
        $this->assertEquals(Product::where('tenant_id', $this->testTenant->id)->get(), $response->getContent());
    }

    /** @test **/
    public function it_retrieves_all_records_when_accessing_via_admin_subdomain()
    {
        $this->actingAs($this->testUser);
        $this->testAdminTenant->users()->save($this->testUser);
        $this->testTenant->domain = $this->testAdminTenant->domain;

        Product::create([
            'name' => 'Another Tenants Product',
            'tenant_id' => Tenant::create([
                'name' => 'Another Tenant',
                'domain' => 'anotherdomain',
            ])->id,
        ]);

        $response = $this->get('products');
        $response->assertStatus(200);
        $this->assertEquals(Product::withoutGlobalScopes()->get(), $response->getContent());
    }
}


================================================
FILE: tests/Feature/Commands/AssignAdminPrivilegesTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature\Commands;

use Spatie\Permission\Models\Role;
use RomegaDigital\Multitenancy\Models\Tenant;
use RomegaDigital\Multitenancy\Tests\TestCase;
use RomegaDigital\Multitenancy\Tests\Fixtures\User;

class AssignAdminPrivilegesTest extends TestCase
{
    /** @test */
    public function it_throws_an_error_and_exits_if_the_given_user_model_class_is_not_found()
    {
        $this->artisan('multitenancy:super-admin', [
                'identifier' => 'test@user.com',
            ])
            ->expectsOutput('User model \App\Models\User can not be found!')
            ->assertExitCode(0);
    }

    /** @test */
    public function it_throws_an_error_and_exits_if_no_user_model_is_found()
    {
        $this->artisan('multitenancy:super-admin', [
                'identifier' => 'fail@user.com',
                '--model' => config('multitenancy.user_model'),
            ])
            ->expectsOutput('User with email `fail@user.com` can not be found!')
            ->assertExitCode(0);
    }

    /** @test */
    public function it_throws_an_error_and_exits_if_no_super_adminitration_role_is_found()
    {
        $this->artisan('multitenancy:super-admin', [
                'identifier' => 'test@user.com',
                '--model' => config('multitenancy.user_model'),
            ])
            ->expectsOutput('Role with name `Super Administrator` can not be found!')
            ->expectsOutput('*     Did you already run `multitenancy:install` command?     *')
            ->assertExitCode(0);
    }

    /** @test */
    public function it_throws_an_error_and_exits_if_no_admin_tenant_is_found()
    {
        Role::create(['name' => 'Super Administrator']);

        $tenant = Tenant::findByDomain('admin');
        $tenant->domain = 'testadmin';
        $tenant->save();

        $this->artisan('multitenancy:super-admin', [
            'identifier' => 'test@user.com',
            '--model' => config('multitenancy.user_model'),
        ])
        ->expectsOutput('Tenant with domain `admin` can not be found!')
        ->expectsOutput('*     Did you already run `multitenancy:install` command?     *')
        ->assertExitCode(0);
    }

    /** @test */
    public function it_assigns_super_administrator_role_and_admin_tenant_to_given_user()
    {
        Role::create(['name' => 'Super Administrator']);

        $this->artisan('multitenancy:super-admin', [
                'identifier' => 'test@user.com',
                '--model' => config('multitenancy.user_model'),
            ])
            ->expectsOutput('User with email test@user.com granted Super-Administration rights.')
            ->assertExitCode(1);

        $user = User::whereEmail('test@user.com')->first();
        $this->assertTrue($user->hasRole('Super Administrator'));
    }
}


================================================
FILE: tests/Feature/Commands/InstallCommandTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature\Commands;

use RomegaDigital\Multitenancy\Tests\TestCase;

class InstallCommandTest extends TestCase
{
    public $setupTestDatabase = false;

    /** @test */
    public function it_published_and_migrates_required_migrations_and_creates_admin_role_and_tenant()
    {
        $this->artisan('multitenancy:install')
            ->expectsOutput('Publishing required migrations...')
            ->expectsOutput('Migrations published!')
            ->expectsOutput('Adding `Super Administrator` Role...')
            ->expectsOutput('Role `Super Administrator` created')
            ->expectsOutput('Adding `admin` domain...')
            ->expectsOutput('Admin domain added successfully!')
            ->assertExitCode(1);
    }
}


================================================
FILE: tests/Feature/Commands/MigrationMakeCommandTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature\Commands;

use RomegaDigital\Multitenancy\Tests\TestCase;

class MigrationMakeCommandTest extends TestCase
{
    public $setupTestDatabase = false;

    /** @test */
    public function it_adds_a_new_migration_with_tenant_id_to_the_specified_table()
    {
        $this->mock(\Illuminate\Filesystem\Filesystem::class)
            ->makePartial()
            ->shouldReceive('put')
            ->once();

        $this->artisan('multitenancy:migration', ['name' => 'testproducts'])
            ->expectsOutput('Multitenancy migration created successfully.')
            ->assertExitCode(1);
    }

    /** @test **/
    public function it_can_handle_multiword_names()
    {
        $this->mock(\Illuminate\Filesystem\Filesystem::class)
            ->makePartial()
            ->shouldReceive('put')
            ->with(\Mockery::any(), \Mockery::pattern('/AddTenantIDColumnToTestNameTable/'))
            ->once();

        $this->artisan('multitenancy:migration', ['name' => 'test_name']);
    }
}


================================================
FILE: tests/Feature/GateTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature;

use Spatie\Permission\Models\Role;
use Illuminate\Support\Facades\Gate;
use RomegaDigital\Multitenancy\Models\Tenant;
use RomegaDigital\Multitenancy\Tests\TestCase;
use RomegaDigital\Multitenancy\Tests\Fixtures\Product;
use RomegaDigital\Multitenancy\Tests\Fixtures\Policies\ProductPolicy;
use RomegaDigital\Multitenancy\Tests\Fixtures\Controllers\ProductController;

class GateTest extends TestCase
{
    /**
     * Define environment setup.
     *
     * @param Illuminate\Foundation\Application $app
     */
    protected function getEnvironmentSetUp($app)
    {
        parent::getEnvironmentSetUp($app);

        $app['router']->resource('products', ProductController::class);

        Gate::policy(Product::class, ProductPolicy::class);
    }

    /**
     * Turn the given URI into a fully qualified URL.
     *
     * @param string $uri
     *
     * @return string
     */
    protected function prepareUrlForRequest($uri)
    {
        $uri = "http://admin.localhost.com/{$uri}";

        return trim($uri, '/');
    }

    /** @test **/
    public function it_does_not_allow_regular_user()
    {
        $this->actingAs($this->testUser);
        $this->testAdminTenant->users()->save($this->testUser);

        $product = Product::create([
            'name' => 'Another Tenants Product',
            'tenant_id' => Tenant::create([
                'name' => 'Another Tenant',
                'domain' => 'anotherdomain',
            ])->id,
        ]);

        $response = $this->get('products/' . $product->id);

        $response->assertForbidden();
    }

    /** @test **/
    public function it_does_not_allow_super_administrator_not_tied_to_admin_subdomain()
    {
        Role::create(['name' => 'Super Administrator']);
        $this->actingAs($this->testUser);
        $this->testUser->assignRole('Super Administrator');

        $product = Product::create([
            'name' => 'Another Tenants Product',
            'tenant_id' => Tenant::create([
                'name' => 'Another Tenant',
                'domain' => 'anotherdomain',
            ])->id,
        ]);

        $response = $this->get('products/' . $product->id);

        $response->assertForbidden();
    }

    /** @test **/
    public function it_does_allow_super_administrator_tied_to_domain()
    {
        Role::create(['name' => 'Super Administrator']);
        $this->actingAs($this->testUser);
        $this->testUser->assignRole('Super Administrator');
        $this->testAdminTenant->users()->save($this->testUser);

        $product = Product::create([
            'name' => 'Another Tenants Product',
            'tenant_id' => Tenant::create([
                'name' => 'Another Tenant',
                'domain' => 'anotherdomain',
            ])->id,
        ]);

        $response = $this->get('products/' . $product->id);

        $response->assertOK();
    }
}


================================================
FILE: tests/Feature/HasTenantTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature;

use RomegaDigital\Multitenancy\Tests\TestCase;
use RomegaDigital\Multitenancy\Contracts\Tenant;
use RomegaDigital\Multitenancy\Tests\Fixtures\User;
use RomegaDigital\Multitenancy\Tests\Fixtures\Controllers\UserController;

class HasTenantTest extends TestCase
{
    /**
     * Define environment setup.
     *
     * @param Illuminate\Foundation\Application $app
     */
    protected function getEnvironmentSetUp($app)
    {
        parent::getEnvironmentSetUp($app);

        $app['router']->resource('users', UserController::class);
    }

    /**
     * Turn the given URI into a fully qualified URL.
     *
     * @param string $uri
     *
     * @return string
     */
    protected function prepareUrlForRequest($uri)
    {
        $uri = "http://{$this->testTenant->domain}.localhost.com/{$uri}";

        return trim($uri, '/');
    }

    /** @test */
    public function it_adds_current_tenant_id_to_user_model_on_create()
    {
        $this->actingAs($this->testUser);
        $this->testTenant->users()->save($this->testUser);

        $this->post('users', [
                'email' => $email = 'another@user.com',
                'name' => 'UserName',
                'password' => 'PassWord',
            ])
            ->assertStatus(201);

        $this->assertContains($this->testTenant->id, User::whereEmail($email)->first()->tenants->pluck('id'));
    }

    /** @test */
    public function it_does_not_add_a_tenant_if_the_the_ignore_tenant_on_user_creation_is_set()
    {
        config(['multitenancy.ignore_tenant_on_user_creation' => true]);
        $this->testTenant->users()->save($this->testUser);
        $otherTenant = resolve(Tenant::class)->create([
            'name' => 'Other',
            'domain' => 'other',
        ]);

        $this->actingAs($this->testUser)
            ->post('users', [
                'email' => $email = 'with@tenant.com',
                'name' => 'UserName',
                'password' => 'PassWord',
                'tenant' => $otherTenant,
            ])
            ->assertStatus(201);

        $this->assertContains($otherTenant->id, $tenantIds = User::whereEmail($email)->first()->tenants->pluck('id'));
        $this->assertNotContains($this->testTenant->id, $tenantIds);
    }
}


================================================
FILE: tests/Feature/Middleware/GuestMiddlewareTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature\Middleware;

use Illuminate\Http\Response;
use RomegaDigital\Multitenancy\Tests\TestCase;
use RomegaDigital\Multitenancy\Exceptions\TenantDoesNotExist;
use RomegaDigital\Multitenancy\Middleware\GuestTenantMiddleware;

class GuestMiddlewareTest extends TestCase
{
    protected $tenantMiddleware;

    public function setUp(): void
    {
        parent::setUp();

        $this->tenantMiddleware = new GuestTenantMiddleware(app('multitenancy'));
    }

    protected function buildRequest($domain)
    {
        app('request')->headers->set('HOST', $domain . '.example.com');

        return $this->tenantMiddleware->handle(app('request'), function () {
            return (new Response())->setContent('<html></html>');
        });
    }

    /** @test */
    public function it_throws_error_if_domain_not_found()
    {
        try {
            $this->buildRequest('testdomain');
            $this->fail('Expected exception not thrown');
        } catch (TenantDoesNotExist $e) { //Not catching a generic Exception or the fail function is also catched
            $this->assertEquals('There is no tenant at domain `testdomain`.', $e->getMessage());
        }
    }

    /** @test **/
    public function it_allows_guest_users_to_access_tenant_scoped_requests()
    {
        $this->assertEquals(
            $this->buildRequest($this->testTenant->domain)->getStatusCode(),
            200
        );
    }
}


================================================
FILE: tests/Feature/Middleware/TenantMiddlewareTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature\Middleware;

use Illuminate\Http\Response;
use Illuminate\Auth\AuthenticationException;
use RomegaDigital\Multitenancy\Tests\TestCase;
use RomegaDigital\Multitenancy\Middleware\TenantMiddleware;
use RomegaDigital\Multitenancy\Exceptions\TenantDoesNotExist;
use RomegaDigital\Multitenancy\Exceptions\UnauthorizedException;

class TenantMiddlewareTest extends TestCase
{
    protected $tenantMiddleware;

    /**
     * Define environment setup.
     *
     * @param Illuminate\Foundation\Application $app
     *
     * @return void
     */
    protected function getEnvironmentSetUp($app)
    {
        parent::getEnvironmentSetUp($app);

        $app['router']->get('/login', function () {
            return 'login';
        })->name(config('multitenancy.redirect_route'));
    }

    public function setUp(): void
    {
        parent::setUp();

        $this->tenantMiddleware = new TenantMiddleware(app('auth'), app('multitenancy'));
    }

    protected function buildRequest($domain)
    {
        app('request')->headers->set('HOST', $domain . '.example.com');

        return $this->tenantMiddleware->handle(app('request'), function () {
            return (new Response())->setContent('<html></html>');
        });
    }

    /** @test */
    public function it_throws_error_if_domain_not_found()
    {
        $this->actingAs($this->testUser);

        try {
            $this->buildRequest('testdomain');
            $this->fail('Expected exception not thrown');
        } catch (TenantDoesNotExist $e) { //Not catching a generic Exception or the fail function is also catched
            $this->assertEquals('There is no tenant at domain `testdomain`.', $e->getMessage());
        }
    }

    /** @test **/
    public function it_throws_error_if_user_is_not_part_of_tenant()
    {
        $this->actingAs($this->testUser);

        try {
            $this->buildRequest($this->testTenant->domain);
            $this->fail('Expected exception not thrown');
        } catch (UnauthorizedException $e) { //Not catching a generic Exception or the fail function is also catched
            $this->assertEquals(403, $e->getStatusCode());
            $this->assertEquals("The authenticated user does not have access to domain `{$this->testTenant->domain}`.", $e->getMessage());
        }
    }

    /** @test **/
    public function it_throws_error_if_user_is_not_logged_in()
    {
        try {
            $this->buildRequest($this->testTenant->domain);
            $this->fail('Expected exception not thrown');
        } catch (AuthenticationException $e) { //Not catching a generic Exception or the fail function is also catched
            $this->assertEquals('Unauthenticated.', $e->getMessage());
        }
    }

    /** @test **/
    public function it_allows_users_who_are_associated_with_a_valid_domain()
    {
        $this->actingAs($this->testUser);

        $this->testTenant->users()->sync($this->testUser);

        $this->assertEquals(
            $this->buildRequest($this->testTenant->domain)->getStatusCode(),
            200
        );
    }
}


================================================
FILE: tests/Feature/MultitenancyTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature;

use Illuminate\Http\Response;
use RomegaDigital\Multitenancy\Exceptions\TenantDoesNotExist;
use RomegaDigital\Multitenancy\Middleware\TenantMiddleware;
use RomegaDigital\Multitenancy\Tests\TestCase;

class MultitenancyTest extends TestCase
{
    protected $tenantMiddleware;

    /**
     * Define environment setup.
     *
     * @param  Illuminate\Foundation\Application  $app
     *
     * @return void
     */
    protected function getEnvironmentSetUp($app)
    {
        parent::getEnvironmentSetUp($app);

        $app['router']->get('/login', function () {
            return 'login';
        })->name(config('multitenancy.redirect_route'));
    }

    public function setUp(): void
    {
        parent::setUp();

        $this->tenantMiddleware = new TenantMiddleware(app('auth'), app('multitenancy'));
    }

    protected function buildRequest($domain)
    {
        app('request')->headers->set('HOST', $domain.'.example.com');

        return $this->tenantMiddleware->handle(app('request'), function () {
            return (new Response())->setContent('<html></html>');
        });
    }

    /** @test */
    public function it_returns_the_current_tenant_when_set_by_middleware()
    {
        $this->actingAs($this->testUser);

        $this->testTenant->users()->sync($this->testUser);

        $this->buildRequest($this->testTenant->domain);

        $this->assertEquals($this->testTenant->domain, app('multitenancy')->currentTenant()->domain);
    }

    /** @test */
    public function it_throws_exception_when_tenant_not_set()
    {
        $this->actingAs($this->testUser);

        $this->testTenant->users()->sync($this->testUser);

        try {
            $this->buildRequest('testdomain');
            app('multitenancy')->currentTenant();
            $this->fail('Expected exception not thrown');
        } catch (TenantDoesNotExist $e) {
            $this->assertEquals('There is no tenant at domain `testdomain`.', $e->getMessage());
        }
    }

    /** @test */
    public function it_throws_exception_when_tenant_not_set_never_touched_middleware()
    {
        $this->actingAs($this->testUser);

        $this->testTenant->users()->sync($this->testUser);

        try {
            app('multitenancy')->currentTenant();
            $this->fail('Expected exception not thrown');
        } catch (TenantDoesNotExist $e) {
            $this->assertEquals('There is no tenant at domain ``.', $e->getMessage());
        }
    }
}


================================================
FILE: tests/Feature/TenantTest.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Feature;

use RomegaDigital\Multitenancy\Exceptions\TenantDoesNotExist;
use RomegaDigital\Multitenancy\Models\Tenant;
use RomegaDigital\Multitenancy\Tests\TestCase;

class TenantTest extends TestCase
{
    /** @test */
    public function it_throws_an_exception_if_domain_not_found()
    {
        $this->expectException(TenantDoesNotExist::class);
        app(Tenant::class)->findByDomain('nonexistentdomain');
    }

    /** @test */
    public function it_is_retrievable_by_domain()
    {
        $permission_by_domain = app(Tenant::class)->findByDomain($this->testTenant->domain);
        $this->assertEquals($this->testTenant->id, $permission_by_domain->id);
    }
}


================================================
FILE: tests/Fixtures/Controllers/ProductController.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Fixtures\Controllers;

use Closure;
use Illuminate\Http\Request;
use PHPUnit\Framework\Assert;
use Illuminate\Contracts\Auth\Access\Gate;
use RomegaDigital\Multitenancy\Tests\Fixtures\Product;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use RomegaDigital\Multitenancy\Middleware\TenantMiddleware;

class ProductController extends \Illuminate\Routing\Controller
{
    use AuthorizesRequests;

    public function __construct()
    {
        $this->middleware([
            function ($request, Closure $next) {
                $route = app('router')->getCurrentRoute();
                Assert::assertSame(ProductController::class, get_class($route->getController()));

                return $next($request);
            },
            TenantMiddleware::class,
        ]);
    }

    public function index()
    {
        return Product::all();
    }

    public function store(Request $request)
    {
        return Product::create([
            'name' => $request->name,
        ]);
    }

    public function show(Product $product)
    {
        app(Gate::class)->authorize('view', $product);

        return $product;
    }
}


================================================
FILE: tests/Fixtures/Controllers/UserController.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Fixtures\Controllers;

use Closure;
use Illuminate\Http\Request;
use PHPUnit\Framework\Assert;
use RomegaDigital\Multitenancy\Models\Tenant;
use RomegaDigital\Multitenancy\Tests\Fixtures\User;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use RomegaDigital\Multitenancy\Middleware\TenantMiddleware;

class UserController extends \Illuminate\Routing\Controller
{
    use AuthorizesRequests;

    public function __construct()
    {
        $this->middleware([
            function ($request, Closure $next) {
                $route = app('router')->getCurrentRoute();
                Assert::assertSame(UserController::class, get_class($route->getController()));

                return $next($request);
            },
            TenantMiddleware::class,
        ]);
    }

    public function store(Request $request)
    {
        if (! $request->has('tenant')) {
            return User::create([
                'email' => $request->email,
                'name' => $request->name,
                'password' => $request->password,
            ]);
        }

        $user = new User([
            'email' => $request->email,
            'name' => $request->name,
            'password' => $request->password,
        ]);

        resolve(Tenant::class)->find($request->tenant)->first()->users()->save($user);

        return $user;
    }
}


================================================
FILE: tests/Fixtures/Policies/ProductPolicy.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Fixtures\Policies;

class ProductPolicy
{
    public function view()
    {
        return false;
    }
}


================================================
FILE: tests/Fixtures/Product.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Fixtures;

use Illuminate\Database\Eloquent\Model;
use RomegaDigital\Multitenancy\Traits\BelongsToTenant;

class Product extends Model
{
    use BelongsToTenant;

    public $timestamps = false;
    protected $fillable = ['name', 'tenant_id'];
}


================================================
FILE: tests/Fixtures/User.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests\Fixtures;

use Illuminate\Auth\Authenticatable;
use Spatie\Permission\Traits\HasRoles;
use Illuminate\Database\Eloquent\Model;
use RomegaDigital\Multitenancy\Traits\HasTenants;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;

class User extends Model implements AuthorizableContract, AuthenticatableContract
{
    use HasRoles, HasTenants, Authorizable, Authenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['email','name','password'];
    public $timestamps = false;
    protected $table = 'users';
    public $guard_name = 'web';
}


================================================
FILE: tests/TestCase.php
================================================
<?php

namespace RomegaDigital\Multitenancy\Tests;

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use RomegaDigital\Multitenancy\Contracts\Tenant;
use Spatie\Permission\PermissionServiceProvider;
use RomegaDigital\Multitenancy\MultitenancyFacade;
use RomegaDigital\Multitenancy\Tests\Fixtures\User;
use Orchestra\Testbench\TestCase as OrchestraTestCase;
use RomegaDigital\Multitenancy\Tests\Fixtures\Product;
use RomegaDigital\Multitenancy\MultitenancyServiceProvider;

class TestCase extends OrchestraTestCase
{
    protected $testUser;
    protected $testTenant;
    protected $testAdminTenant;
    protected $testProduct;

    public $setupTestDatabase = true;

    /**
     * Set up the environment.
     *
     * @param \Illuminate\Foundation\Application $app
     */
    protected function getEnvironmentSetUp($app)
    {
        $app['config']->set('multitenancy.user_model', User::class);
        $app['config']->set('auth.providers.users.model', config('multitenancy.user_model'));
        $app['config']->set('auth.guards.web.provider', 'users');
    }

    /**
     * Load package service provider.
     *
     * @param \Illuminate\Foundation\Application $app
     *
     * @return array
     */
    protected function getPackageProviders($app)
    {
        return [
            MultitenancyServiceProvider::class,
            PermissionServiceProvider::class,
        ];
    }

    /**
     * Load package alias.
     *
     * @param \Illuminate\Foundation\Application $app
     *
     * @return array
     */
    protected function getPackageAliases($app)
    {
        return [
            'Multitenancy' => MultitenancyFacade::class,
        ];
    }

    public function setUp(): void
    {
        parent::setUp();

        if ($this->setupTestDatabase) {
            $this->setUpDatabase($this->app);

            $this->testUser = User::first();
            $this->testTenant = app(Tenant::class)->find(1);
            $this->testAdminTenant = app(Tenant::class)->find(2);
            $this->testProduct = Product::first();
        }
    }

    /**
     * Define database migrations.
     *
     * @return void
     */
    protected function defineDatabaseMigrations()
    {
        $this->loadLaravelMigrations();
    }

    /**
     * Set up the database.
     *
     * @param \Illuminate\Foundation\Application $app
     */
    protected function setUpDatabase($app)
    {
        $this->loadMigrationsFrom(realpath(__DIR__ . '/../migrations'));
        $this->artisan('migrate')->run();

        $app[Tenant::class]->create([
            'name' => 'Tenant Name',
            'domain' => 'masterdomain',
        ]);
        $app[Tenant::class]->create([
            'name' => 'Admin',
            'domain' => 'admin',
        ]);

        User::create([
            'name' => "Test User",
            'email' => 'test@user.com',
            'password' => 'testPassword',
        ]);

        Schema::create('products', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->unsignedInteger('tenant_id');
            $table->foreign('tenant_id')
                ->references('id')
                ->on('tenants')
                ->onDelete('cascade');
            $table->softDeletes();
        });
        Product::create([
            'name' => 'Product 1',
            'tenant_id' => '1',
        ]);
    }
}
Download .txt
gitextract_nf3oca15/

├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── config/
│   └── multitenancy.php
├── migrations/
│   └── create_tenants_table.php.stub
├── phpunit.xml
├── src/
│   ├── Commands/
│   │   ├── AssignAdminPrivileges.php
│   │   ├── InstallCommand.php
│   │   ├── MigrationMakeCommand.php
│   │   └── stubs/
│   │       └── add_tenancy_to_table.stub
│   ├── Contracts/
│   │   └── Tenant.php
│   ├── Exceptions/
│   │   ├── TenantDoesNotExist.php
│   │   └── UnauthorizedException.php
│   ├── Middleware/
│   │   ├── GuestTenantMiddleware.php
│   │   └── TenantMiddleware.php
│   ├── Models/
│   │   └── Tenant.php
│   ├── Multitenancy.php
│   ├── MultitenancyFacade.php
│   ├── MultitenancyServiceProvider.php
│   └── Traits/
│       ├── BelongsToTenant.php
│       └── HasTenants.php
└── tests/
    ├── Databases/
    │   └── MigrateDatabaseTest.php
    ├── Feature/
    │   ├── BelongsToTenantTest.php
    │   ├── Commands/
    │   │   ├── AssignAdminPrivilegesTest.php
    │   │   ├── InstallCommandTest.php
    │   │   └── MigrationMakeCommandTest.php
    │   ├── GateTest.php
    │   ├── HasTenantTest.php
    │   ├── Middleware/
    │   │   ├── GuestMiddlewareTest.php
    │   │   └── TenantMiddlewareTest.php
    │   ├── MultitenancyTest.php
    │   └── TenantTest.php
    ├── Fixtures/
    │   ├── Controllers/
    │   │   ├── ProductController.php
    │   │   └── UserController.php
    │   ├── Policies/
    │   │   └── ProductPolicy.php
    │   ├── Product.php
    │   └── User.php
    └── TestCase.php
Download .txt
SYMBOL INDEX (140 symbols across 31 files)

FILE: src/Commands/AssignAdminPrivileges.php
  class AssignAdminPrivileges (line 11) | class AssignAdminPrivileges extends Command
    method __construct (line 42) | public function __construct(Multitenancy $multitenancy)
    method handle (line 54) | public function handle()
    method getUser (line 93) | protected function getUser($userModel, $column, $identifier)
    method getAdminRole (line 107) | protected function getAdminRole()
    method getAdminTenant (line 121) | protected function getAdminTenant()
    method cancel (line 139) | protected function cancel($model, $column, $identifier)
    method modelNotFound (line 157) | protected function modelNotFound($model, $column, $identifier)

FILE: src/Commands/InstallCommand.php
  class InstallCommand (line 8) | class InstallCommand extends Command
    method __construct (line 39) | public function __construct(Multitenancy $multitenancy)
    method handle (line 51) | public function handle()
    method handleMigrations (line 65) | protected function handleMigrations()
    method addSuperAdminRole (line 92) | protected function addSuperAdminRole()
    method addAdminTenant (line 109) | protected function addAdminTenant()

FILE: src/Commands/MigrationMakeCommand.php
  class MigrationMakeCommand (line 10) | class MigrationMakeCommand extends GeneratorCommand
    method __construct (line 48) | public function __construct(Filesystem $files, Composer $composer)
    method handle (line 60) | public function handle()
    method getStub (line 76) | protected function getStub()
    method buildClass (line 90) | protected function buildClass($name)
    method replaceClass (line 109) | protected function replaceClass($stub, $name)
    method getPath (line 123) | protected function getPath($name)

FILE: src/Contracts/Tenant.php
  type Tenant (line 7) | interface Tenant
    method users (line 14) | public function users(): BelongsToMany;
    method findByDomain (line 25) | public static function findByDomain(string $domain): self;

FILE: src/Exceptions/TenantDoesNotExist.php
  class TenantDoesNotExist (line 7) | class TenantDoesNotExist extends InvalidArgumentException
    method forDomain (line 16) | public static function forDomain(string $domain): self

FILE: src/Exceptions/UnauthorizedException.php
  class UnauthorizedException (line 7) | class UnauthorizedException extends HttpException
    method forDomain (line 17) | public static function forDomain(string $domain): self
    method notLoggedIn (line 31) | public static function notLoggedIn(): self

FILE: src/Middleware/GuestTenantMiddleware.php
  class GuestTenantMiddleware (line 8) | class GuestTenantMiddleware
    method __construct (line 21) | public function __construct(Multitenancy $multitenancy)
    method handle (line 35) | public function handle($request, Closure $next)

FILE: src/Middleware/TenantMiddleware.php
  class TenantMiddleware (line 12) | class TenantMiddleware extends Middleware
    method __construct (line 25) | public function __construct(Auth $auth, Multitenancy $multitenancy)
    method redirectTo (line 39) | protected function redirectTo($request)
    method handle (line 57) | public function handle($request, Closure $next, ...$guards)
    method authorizedToAccessTenant (line 81) | protected function authorizedToAccessTenant(Tenant $tenant)

FILE: src/Models/Tenant.php
  class Tenant (line 11) | class Tenant extends Model implements TenantContract
    method __construct (line 29) | public function __construct(array $attributes = [])
    method users (line 41) | public function users(): BelongsToMany
    method findByDomain (line 56) | public static function findByDomain(string $domain): TenantContract

FILE: src/Multitenancy.php
  class Multitenancy (line 9) | class Multitenancy
    method __construct (line 38) | public function __construct()
    method setTenant (line 51) | public function setTenant(Tenant $tenant)
    method currentTenant (line 63) | public function currentTenant(): Tenant
    method applyTenantScope (line 76) | public function applyTenantScope(Model $model)
    method newModel (line 100) | public function newModel(Model $model)
    method applyTenantScopeToDeferredModels (line 117) | public function applyTenantScopeToDeferredModels()
    method getTenantClass (line 131) | public function getTenantClass(): Tenant
    method getCurrentSubDomain (line 143) | public function getCurrentSubDomain(): string
    method getSubDomainBasedOnHTTPHost (line 164) | protected function getSubDomainBasedOnHTTPHost(): string
    method getSubDomainBasedOnBaseURL (line 190) | protected function getSubDomainBasedOnBaseURL(string $baseURL): string
    method receiveTenantFromRequest (line 212) | public function receiveTenantFromRequest()

FILE: src/MultitenancyFacade.php
  class MultitenancyFacade (line 7) | class MultitenancyFacade extends Facade
    method getFacadeAccessor (line 14) | protected static function getFacadeAccessor()

FILE: src/MultitenancyServiceProvider.php
  class MultitenancyServiceProvider (line 14) | class MultitenancyServiceProvider extends ServiceProvider
    method boot (line 21) | public function boot(Filesystem $filesystem)
    method register (line 43) | public function register()
    method registerPublishing (line 62) | protected function registerPublishing(Filesystem $filesystem)
    method registerCommands (line 76) | protected function registerCommands()
    method registerModelBindings (line 88) | protected function registerModelBindings()
    method getMigrationFileName (line 100) | protected function getMigrationFileName(Filesystem $filesystem): string

FILE: src/Traits/BelongsToTenant.php
  type BelongsToTenant (line 7) | trait BelongsToTenant
    method bootBelongsToTenant (line 13) | public static function bootBelongsToTenant()
    method tenant (line 27) | public function tenant()

FILE: src/Traits/HasTenants.php
  type HasTenants (line 7) | trait HasTenants
    method bootHasTenants (line 12) | public static function bootHasTenants()
    method tenants (line 32) | public function tenants()

FILE: tests/Databases/MigrateDatabaseTest.php
  class MigrateDatabaseTest (line 7) | class MigrateDatabaseTest extends TestCase
    method it_runs_the_migrations (line 10) | public function it_runs_the_migrations()

FILE: tests/Feature/BelongsToTenantTest.php
  class BelongsToTenantTest (line 10) | class BelongsToTenantTest extends TestCase
    method getEnvironmentSetUp (line 17) | protected function getEnvironmentSetUp($app)
    method prepareUrlForRequest (line 31) | protected function prepareUrlForRequest($uri)
    method it_adds_current_tenant_id_to_model_on_create (line 39) | public function it_adds_current_tenant_id_to_model_on_create()
    method it_only_retrieves_records_scoped_to_current_subdomain (line 52) | public function it_only_retrieves_records_scoped_to_current_subdomain()
    method it_retrieves_all_records_when_accessing_via_admin_subdomain (line 71) | public function it_retrieves_all_records_when_accessing_via_admin_subd...

FILE: tests/Feature/Commands/AssignAdminPrivilegesTest.php
  class AssignAdminPrivilegesTest (line 10) | class AssignAdminPrivilegesTest extends TestCase
    method it_throws_an_error_and_exits_if_the_given_user_model_class_is_not_found (line 13) | public function it_throws_an_error_and_exits_if_the_given_user_model_c...
    method it_throws_an_error_and_exits_if_no_user_model_is_found (line 23) | public function it_throws_an_error_and_exits_if_no_user_model_is_found()
    method it_throws_an_error_and_exits_if_no_super_adminitration_role_is_found (line 34) | public function it_throws_an_error_and_exits_if_no_super_adminitration...
    method it_throws_an_error_and_exits_if_no_admin_tenant_is_found (line 46) | public function it_throws_an_error_and_exits_if_no_admin_tenant_is_fou...
    method it_assigns_super_administrator_role_and_admin_tenant_to_given_user (line 64) | public function it_assigns_super_administrator_role_and_admin_tenant_t...

FILE: tests/Feature/Commands/InstallCommandTest.php
  class InstallCommandTest (line 7) | class InstallCommandTest extends TestCase
    method it_published_and_migrates_required_migrations_and_creates_admin_role_and_tenant (line 12) | public function it_published_and_migrates_required_migrations_and_crea...

FILE: tests/Feature/Commands/MigrationMakeCommandTest.php
  class MigrationMakeCommandTest (line 7) | class MigrationMakeCommandTest extends TestCase
    method it_adds_a_new_migration_with_tenant_id_to_the_specified_table (line 12) | public function it_adds_a_new_migration_with_tenant_id_to_the_specifie...
    method it_can_handle_multiword_names (line 25) | public function it_can_handle_multiword_names()

FILE: tests/Feature/GateTest.php
  class GateTest (line 13) | class GateTest extends TestCase
    method getEnvironmentSetUp (line 20) | protected function getEnvironmentSetUp($app)
    method prepareUrlForRequest (line 36) | protected function prepareUrlForRequest($uri)
    method it_does_not_allow_regular_user (line 44) | public function it_does_not_allow_regular_user()
    method it_does_not_allow_super_administrator_not_tied_to_admin_subdomain (line 63) | public function it_does_not_allow_super_administrator_not_tied_to_admi...
    method it_does_allow_super_administrator_tied_to_domain (line 83) | public function it_does_allow_super_administrator_tied_to_domain()

FILE: tests/Feature/HasTenantTest.php
  class HasTenantTest (line 10) | class HasTenantTest extends TestCase
    method getEnvironmentSetUp (line 17) | protected function getEnvironmentSetUp($app)
    method prepareUrlForRequest (line 31) | protected function prepareUrlForRequest($uri)
    method it_adds_current_tenant_id_to_user_model_on_create (line 39) | public function it_adds_current_tenant_id_to_user_model_on_create()
    method it_does_not_add_a_tenant_if_the_the_ignore_tenant_on_user_creation_is_set (line 55) | public function it_does_not_add_a_tenant_if_the_the_ignore_tenant_on_u...

FILE: tests/Feature/Middleware/GuestMiddlewareTest.php
  class GuestMiddlewareTest (line 10) | class GuestMiddlewareTest extends TestCase
    method setUp (line 14) | public function setUp(): void
    method buildRequest (line 21) | protected function buildRequest($domain)
    method it_throws_error_if_domain_not_found (line 31) | public function it_throws_error_if_domain_not_found()
    method it_allows_guest_users_to_access_tenant_scoped_requests (line 42) | public function it_allows_guest_users_to_access_tenant_scoped_requests()

FILE: tests/Feature/Middleware/TenantMiddlewareTest.php
  class TenantMiddlewareTest (line 12) | class TenantMiddlewareTest extends TestCase
    method getEnvironmentSetUp (line 23) | protected function getEnvironmentSetUp($app)
    method setUp (line 32) | public function setUp(): void
    method buildRequest (line 39) | protected function buildRequest($domain)
    method it_throws_error_if_domain_not_found (line 49) | public function it_throws_error_if_domain_not_found()
    method it_throws_error_if_user_is_not_part_of_tenant (line 62) | public function it_throws_error_if_user_is_not_part_of_tenant()
    method it_throws_error_if_user_is_not_logged_in (line 76) | public function it_throws_error_if_user_is_not_logged_in()
    method it_allows_users_who_are_associated_with_a_valid_domain (line 87) | public function it_allows_users_who_are_associated_with_a_valid_domain()

FILE: tests/Feature/MultitenancyTest.php
  class MultitenancyTest (line 10) | class MultitenancyTest extends TestCase
    method getEnvironmentSetUp (line 21) | protected function getEnvironmentSetUp($app)
    method setUp (line 30) | public function setUp(): void
    method buildRequest (line 37) | protected function buildRequest($domain)
    method it_returns_the_current_tenant_when_set_by_middleware (line 47) | public function it_returns_the_current_tenant_when_set_by_middleware()
    method it_throws_exception_when_tenant_not_set (line 59) | public function it_throws_exception_when_tenant_not_set()
    method it_throws_exception_when_tenant_not_set_never_touched_middleware (line 75) | public function it_throws_exception_when_tenant_not_set_never_touched_...

FILE: tests/Feature/TenantTest.php
  class TenantTest (line 9) | class TenantTest extends TestCase
    method it_throws_an_exception_if_domain_not_found (line 12) | public function it_throws_an_exception_if_domain_not_found()
    method it_is_retrievable_by_domain (line 19) | public function it_is_retrievable_by_domain()

FILE: tests/Fixtures/Controllers/ProductController.php
  class ProductController (line 13) | class ProductController extends \Illuminate\Routing\Controller
    method __construct (line 17) | public function __construct()
    method index (line 30) | public function index()
    method store (line 35) | public function store(Request $request)
    method show (line 42) | public function show(Product $product)

FILE: tests/Fixtures/Controllers/UserController.php
  class UserController (line 13) | class UserController extends \Illuminate\Routing\Controller
    method __construct (line 17) | public function __construct()
    method store (line 30) | public function store(Request $request)

FILE: tests/Fixtures/Policies/ProductPolicy.php
  class ProductPolicy (line 5) | class ProductPolicy
    method view (line 7) | public function view()

FILE: tests/Fixtures/Product.php
  class Product (line 8) | class Product extends Model

FILE: tests/Fixtures/User.php
  class User (line 13) | class User extends Model implements AuthorizableContract, Authenticatabl...

FILE: tests/TestCase.php
  class TestCase (line 15) | class TestCase extends OrchestraTestCase
    method getEnvironmentSetUp (line 29) | protected function getEnvironmentSetUp($app)
    method getPackageProviders (line 43) | protected function getPackageProviders($app)
    method getPackageAliases (line 58) | protected function getPackageAliases($app)
    method setUp (line 65) | public function setUp(): void
    method defineDatabaseMigrations (line 84) | protected function defineDatabaseMigrations()
    method setUpDatabase (line 94) | protected function setUpDatabase($app)
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (79K chars).
[
  {
    "path": ".gitignore",
    "chars": 39,
    "preview": ".DS_Store\n/vendor\n.phpunit.result.cache"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2019 Romega Digital\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 7220,
    "preview": "# Multitenancy Laravel Package\n\n[![Total Downloads](https://img.shields.io/packagist/dt/romegadigital/multitenancy.svg?s"
  },
  {
    "path": "composer.json",
    "chars": 841,
    "preview": "{\n    \"name\": \"romegadigital/multitenancy\",\n    \"description\": \"Adds domain based multitenancy to Laravel applications.\""
  },
  {
    "path": "config/multitenancy.php",
    "chars": 4349,
    "preview": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | User Model\n"
  },
  {
    "path": "migrations/create_tenants_table.php.stub",
    "chars": 1932,
    "preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Fa"
  },
  {
    "path": "phpunit.xml",
    "chars": 889,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"vendor/autoload.php\"\n         backupGlobals=\"false\"\n         "
  },
  {
    "path": "src/Commands/AssignAdminPrivileges.php",
    "chars": 4356,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse Spatie\\Permission\\Models\\Role"
  },
  {
    "path": "src/Commands/InstallCommand.php",
    "chars": 2931,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse RomegaDigital\\Multitenancy\\Mu"
  },
  {
    "path": "src/Commands/MigrationMakeCommand.php",
    "chars": 2898,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Commands;\n\nuse Illuminate\\Console\\GeneratorCommand;\nuse Illuminate\\Filesyste"
  },
  {
    "path": "src/Commands/stubs/add_tenancy_to_table.stub",
    "chars": 817,
    "preview": "<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migratio"
  },
  {
    "path": "src/Contracts/Tenant.php",
    "chars": 572,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Contracts;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\n\ninter"
  },
  {
    "path": "src/Exceptions/TenantDoesNotExist.php",
    "chars": 429,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Exceptions;\n\nuse InvalidArgumentException;\n\nclass TenantDoesNotExist extends"
  },
  {
    "path": "src/Exceptions/UnauthorizedException.php",
    "chars": 787,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Exceptions;\n\nuse Symfony\\Component\\HttpKernel\\Exception\\HttpException;\n\nclas"
  },
  {
    "path": "src/Middleware/GuestTenantMiddleware.php",
    "chars": 1029,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Middleware;\n\nuse Closure;\nuse RomegaDigital\\Multitenancy\\Multitenancy;\n\nclas"
  },
  {
    "path": "src/Middleware/TenantMiddleware.php",
    "chars": 2401,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Middleware;\n\nuse Closure;\nuse RomegaDigital\\Multitenancy\\Multitenancy;\nuse I"
  },
  {
    "path": "src/Models/Tenant.php",
    "chars": 1628,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Elo"
  },
  {
    "path": "src/Multitenancy.php",
    "chars": 5426,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\B"
  },
  {
    "path": "src/MultitenancyFacade.php",
    "chars": 294,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\nclass MultitenancyFacade extends F"
  },
  {
    "path": "src/MultitenancyServiceProvider.php",
    "chars": 3358,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Gate;\nus"
  },
  {
    "path": "src/Traits/BelongsToTenant.php",
    "chars": 734,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Traits;\n\nuse RomegaDigital\\Multitenancy\\Multitenancy;\n\ntrait BelongsToTenant"
  },
  {
    "path": "src/Traits/HasTenants.php",
    "chars": 939,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Traits;\n\nuse RomegaDigital\\Multitenancy\\Multitenancy;\n\ntrait HasTenants\n{\n  "
  },
  {
    "path": "tests/Databases/MigrateDatabaseTest.php",
    "chars": 736,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Databases;\n\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\n\nclass Migr"
  },
  {
    "path": "tests/Feature/BelongsToTenantTest.php",
    "chars": 2694,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse RomegaDigital\\Multitenancy\\Models\\Tenant;\nuse RomegaDigi"
  },
  {
    "path": "tests/Feature/Commands/AssignAdminPrivilegesTest.php",
    "chars": 2829,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Commands;\n\nuse Spatie\\Permission\\Models\\Role;\nuse RomegaDigita"
  },
  {
    "path": "tests/Feature/Commands/InstallCommandTest.php",
    "chars": 786,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Commands;\n\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\n\ncla"
  },
  {
    "path": "tests/Feature/Commands/MigrationMakeCommandTest.php",
    "chars": 1055,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Commands;\n\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\n\ncla"
  },
  {
    "path": "tests/Feature/GateTest.php",
    "chars": 2933,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse Spatie\\Permission\\Models\\Role;\nuse Illuminate\\Support\\Fa"
  },
  {
    "path": "tests/Feature/HasTenantTest.php",
    "chars": 2313,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\nuse RomegaDig"
  },
  {
    "path": "tests/Feature/Middleware/GuestMiddlewareTest.php",
    "chars": 1466,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Middleware;\n\nuse Illuminate\\Http\\Response;\nuse RomegaDigital\\M"
  },
  {
    "path": "tests/Feature/Middleware/TenantMiddlewareTest.php",
    "chars": 3130,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Middleware;\n\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Auth"
  },
  {
    "path": "tests/Feature/MultitenancyTest.php",
    "chars": 2526,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse Illuminate\\Http\\Response;\nuse RomegaDigital\\Multitenancy"
  },
  {
    "path": "tests/Feature/TenantTest.php",
    "chars": 722,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse RomegaDigital\\Multitenancy\\Exceptions\\TenantDoesNotExist"
  },
  {
    "path": "tests/Fixtures/Controllers/ProductController.php",
    "chars": 1192,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Controllers;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse P"
  },
  {
    "path": "tests/Fixtures/Controllers/UserController.php",
    "chars": 1406,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Controllers;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse P"
  },
  {
    "path": "tests/Fixtures/Policies/ProductPolicy.php",
    "chars": 155,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Policies;\n\nclass ProductPolicy\n{\n    public function view()\n "
  },
  {
    "path": "tests/Fixtures/Product.php",
    "chars": 296,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse RomegaDigital\\M"
  },
  {
    "path": "tests/Fixtures/User.php",
    "chars": 817,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures;\n\nuse Illuminate\\Auth\\Authenticatable;\nuse Spatie\\Permission\\"
  },
  {
    "path": "tests/TestCase.php",
    "chars": 3438,
    "preview": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests;\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schem"
  }
]

About this extraction

This page contains the full source code of the romegasoftware/Multitenancy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (71.7 KB), approximately 17.8k tokens, and a symbol index with 140 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!