[
  {
    "path": ".gitignore",
    "content": ".DS_Store\n/vendor\n.phpunit.result.cache"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Romega Digital\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Multitenancy Laravel Package\n\n[![Total Downloads](https://img.shields.io/packagist/dt/romegadigital/multitenancy.svg?style=flat-square)](https://packagist.org/packages/romegadigital/multitenancy)\n\nThis 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.\n\n**Note:** Any resources saved while accessing a scoped subdomain will automatically be saved against the current tenant, based on subdomain.\n\n**Note:** The `admin` subdomain is reserved for the package to remove all scopes from users with a `Super Administrator` role.\n\n## Table of Contents\n- [Installation](#installation)\n- [Usage](#usage)\n- [Console Commands](#console-commands)\n- [Nova Management](#managing-with-nova)\n- [Testing](#testing-package)\n\n## Installation\n\n#### 1. Use composer to install the package:\n\n``` bash\ncomposer require romegadigital/multitenancy\n```\n\nIn Laravel 5.5 and newer, the service provider gets registered automatically. For older versions, add the service provider in the `config/app.php` file:\n\n```php\n'providers' => [\n    // ...\n    RomegaDigital\\Multitenancy\\MultitenancyServiceProvider::class,\n];\n```\n\n#### 2. Publish the config file\n\n```bash\nphp artisan vendor:publish --provider=\"RomegaDigital\\Multitenancy\\MultitenancyServiceProvider\" --tag=\"config\"\n```\n\n#### 3. Run the setup\n\n```bash\nphp artisan multitenancy:install\n```\n\nThis command will:\n- Publish and migrate required migrations\n- Add a `Super Administrator` role and `access admin` permission\n- Create an `admin` Tenant model\n\n\n#### 4. Update your `.env` file\nThe package needs to know your base URL so it can determine what constitutes a tenant by the subdomain.\n\nAdd this to your `.env` file: `MULTITENANCY_BASE_URL=`\n\n#### 5. Update your User model\n\nApply the `RomegaDigital\\Multitenancy\\Traits\\HasTenants` and `Spatie\\Permission\\Traits\\HasRoles` traits to your User model(s):\n\n```php\nuse Spatie\\Permission\\Traits\\HasRoles;\nuse RomegaDigital\\Multitenancy\\Traits\\HasTenants;\nuse Illuminate\\Foundation\\Auth\\User as Authenticatable;\n\nclass User extends Authenticatable\n{\n    use HasTenants, HasRoles;\n    // ...\n}\n```\n\n\n## Usage\n\n\nTenants require a name to identify the tenant and a subdomain that is associated with that user. Example:\n\n`tenant1.example.com`\n\n`tenant2.example.com`\n\n\n**Note:** You define the base url `example.com` in the `config/multitenancy.php` file.\n\n\nThese Tenants could be added to the database like so:\n\n```php\nTenant::create([\n    'name'    => 'An Identifying Name',\n    'domain'  => 'tenant1'\n]);\nTenant::create([\n    'name'    => 'A Second Customer',\n    'domain'  => 'tenant2'\n]);\n```\n\nYou can then attach user models to the Tenant:\n\n```php\n$user = User::first();\nTenant::first()->users()->save($user);\n```\n\nCreate Tenants, associate them with Users, and define access rules using provided Middleware. Check [the detailed usage guide](#detailed-usage-guide) for examples.\n\n\n## Detailed Usage Guide\n### 1. **Models and relationships:** \nUse 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.\n\n\n### 2. **Middleware:** \nAdd `TenantMiddleware` and `GuestTenantMiddleware` to your `app/Http/Kernel.php` file and apply them to routes.\n\n#### Tenant Middleware\n\n```php\nprotected $middlewareAliases = [\n    // ...\n    'tenant.auth' => \\RomegaDigital\\Multitenancy\\Middleware\\TenantMiddleware::class,\n];\n```\n\nThen you can bring multitenancy to your routes using middleware rules:\n\n```php\nRoute::group(['middleware' => ['tenant.auth']], function () {\n    // ...\n});\n```\n\n#### Guest Tenant Middleware\n\nThis 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.\n\n```php\nprotected $middlewareAliases = [\n    // ...\n    'tenant.guest' => \\RomegaDigital\\Multitenancy\\Middleware\\GuestTenantMiddleware::class,\n];\n```\n\nThen you can bring multitenancy to your routes using middleware rules:\n\n```php\nRoute::group(['middleware' => ['tenant.guest']], function () {\n    // ...\n});\n```\n\n\n### 3. **Tenant Assignment for Models:** \nMake 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.\n\nFor 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.\n\n```php\nuse Illuminate\\Database\\Eloquent\\Model;\nuse RomegaDigital\\Multitenancy\\Traits\\BelongsToTenant;\n\nclass Product extends Model\n{\n    use BelongsToTenant;\n\n    // ...\n}\n```\n**Add tenancy to a model's table:** `php artisan multitenancy:migration products`\n\n### 4. **Access to Current Tenant:** \nUse `app('multitenancy')->currentTenant()` to get the current tenant model.\n\n### 5. **Admin Domain Access:** \nAssign the `Super Administrator` role to a user to enable access to the `admin` subdomain. Manually create an admin portal if necessary.\n\n### 6. **Auto-assign Users to Tenants:** \nEnable `ignore_tenant_on_user_creation` setting to automatically assign users to the Tenant subdomain on which they are created.\n\n### 7. **Give a user `Super Administration` rights:**\n\nIn 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.\n\nThe 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.\n\nWhen 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.\n\nGive a user `Super Administration` rights: `php artisan multitenancy:super-admin admin@example.com`\n\n## Managing with Nova\n\nYou can manage the resources of this package in Nova with the [MultitenancyNovaTool](https://github.com/romegadigital/MultitenancyNovaTool).\n\n## Testing Package\n\nRun tests with the command:\n\n`php vendor/bin/testbench package:test`\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"romegadigital/multitenancy\",\n    \"description\": \"Adds domain based multitenancy to Laravel applications.\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Braden Keith\",\n            \"email\": \"bkeith@romegadigital.com\"\n        }\n    ],\n    \"autoload\": {\n        \"psr-4\": {\n            \"RomegaDigital\\\\Multitenancy\\\\\": \"src/\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"RomegaDigital\\\\Multitenancy\\\\Tests\\\\\": \"tests/\"\n        }\n    },\n    \"require\": {\n        \"spatie/laravel-permission\": \"^6.9.0\"\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"RomegaDigital\\\\Multitenancy\\\\MultitenancyServiceProvider\"\n            ]\n        }\n    },\n    \"require-dev\": {\n        \"orchestra/testbench\": \"^7\",\n        \"nunomaduro/collision\": \"^6.0\"\n    }\n}\n"
  },
  {
    "path": "config/multitenancy.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | User Model\n    |--------------------------------------------------------------------------\n    |\n    | This is the model you are using for Users that will be attached to the\n    | Tenant instance. Users must be attached to domains in order to have\n    | access to tenant instance.\n    */\n\n    'user_model' => \\App\\Models\\User::class,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Base URL\n    |--------------------------------------------------------------------------\n    |\n    | This is the URL you would like to serve as the base of your app. It\n    | should not contain a scheme (ie: http://, https://).\n    | By default, it will attempt to use the host name with the TLD and domain\n    | name stripped.\n    |\n    | Default: null\n    */\n\n    'base_url' => env('MULTITENANCY_BASE_URL', null),\n\n    /*\n    |--------------------------------------------------------------------------\n    | Roles\n    |--------------------------------------------------------------------------\n    |\n    | The values (on the right) determine how the roles with\n    | keys (on the left) are being named in the database.\n    */\n\n    'roles' => [\n        // What the Super Administrator is called in your app\n        'super_admin' => 'Super Administrator',\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Policy Files\n    |--------------------------------------------------------------------------\n    |\n    | The policy file to use when using [MultitenancyNovaTool]\n    | (https://github.com/romegasoftware/MultitenancyNovaTool)\n    */\n\n    'policies' => [\n        'role' => \\RomegaDigital\\MultitenancyNovaTool\\Policies\\RolePolicy::class,\n        'permission' => \\RomegaDigital\\MultitenancyNovaTool\\Policies\\PermissionPolicy::class,\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Nova Resource Files\n    |--------------------------------------------------------------------------\n    |\n    | The Nova resources to use when using [MultitenancyNovaTool]\n    | (https://github.com/romegasoftware/MultitenancyNovaTool)\n    */\n\n    'resources' => [\n        'role' => \\Vyuldashev\\NovaPermission\\Role::class,\n        'permission' => \\Vyuldashev\\NovaPermission\\Permission::class,\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Tenant Model\n    |--------------------------------------------------------------------------\n    |\n    | This is the model you are using for Tenants that will be attached to the\n    | User instance. It would be recommended to extend the Tenant model as\n    | defined in the package, but if you replace it, be sure to implement\n    | the RomegaDigital\\Multitenancy\\Contracts\\Tenant contract.\n    */\n\n    'tenant_model' => \\RomegaDigital\\Multitenancy\\Models\\Tenant::class,\n\n    'table_names' => [\n        /**\n         * We need to know which table to setup foreign relationships on.\n         */\n\n        'users' => 'users',\n\n        /**\n         * If overwriting `tenant_model`, you may also wish to define a new table\n         */\n\n        'tenants' => 'tenants',\n\n        /**\n         * Define the relationship table for the belongsToMany relationship\n         */\n\n        'tenant_user' => 'tenant_user',\n    ],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Redirect Route\n    |--------------------------------------------------------------------------\n    |\n    | This is the name of the route users who aren't logged in will be redirected to\n    */\n\n    'redirect_route' => 'login',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Ignore Tenant on User creation\n    |--------------------------------------------------------------------------\n    |\n    | By default a user is assigned the tenant it is created on. If you create\n    | a user while being on the `admin` tenant, this would assign the created\n    | user the `admin` tenant automatically. If you don't want to get tenants\n    | assigned to users automatically simply disable this setting by setting\n    | it to false.\n    */\n\n    'ignore_tenant_on_user_creation' => false,\n];\n"
  },
  {
    "path": "migrations/create_tenants_table.php.stub",
    "content": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Support\\Str;\n\nclass CreateTenantsTable extends Migration\n{\n    /**\n     * Run the migrations.\n     *\n     * @return void\n     */\n    public function up()\n    {\n        $tableNames = config('multitenancy.table_names');\n\n        Schema::create($tableNames['tenants'], function (Blueprint $table) {\n            $table->bigIncrements('id');\n            $table->string('name')->unique();\n            $table->string('domain')->unique();\n            $table->softDeletes();\n            $table->timestamps();\n        });\n\n        Schema::create($tableNames['tenant_user'], function (Blueprint $table) use ($tableNames) {\n            $table->bigIncrements('id');\n\n            $table->unsignedBigInteger('tenant_id');\n            $table->foreign(Str::singular($tableNames['tenants']).'_id')\n                ->references('id')\n                ->on($tableNames['tenants'])\n                ->onDelete('cascade');\n\n            $table->unsignedBigInteger('user_id');\n            $table->foreign(Str::singular($tableNames['users']).'_id')\n                ->references('id')\n                ->on($tableNames['users'])\n                ->onDelete('cascade');\n\n            $table->timestamps();\n            $table->softDeletes();\n        });\n    }\n\n    /**\n     * Reverse the migrations.\n     *\n     * @return void\n     */\n    public function down()\n    {\n        $tableNames = config('multitenancy.table_names');\n\n        Schema::table($tableNames['tenant_user'], function (Blueprint $table) use ($tableNames) {\n            $table->dropForeign([Str::singular($tableNames['tenants']).'_id']);\n            $table->dropForeign([Str::singular($tableNames['users']).'_id']);\n        });\n\n        Schema::dropIfExists($tableNames['tenants']);\n        Schema::dropIfExists($tableNames['tenant_user']);\n    }\n}\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"vendor/autoload.php\"\n         backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         colors=\"true\"\n         verbose=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\">\n    <testsuites>\n        <testsuite name=\"Multitenancy Test Suite\">\n            <directory>tests</directory>\n        </testsuite>\n    </testsuites>\n    <filter>\n        <whitelist>\n            <directory suffix=\".php\">src/</directory>\n        </whitelist>\n    </filter>\n    <php>\n        <env name=\"APP_ENV\" value=\"testing\"/>\n        <env name=\"BCRYPT_ROUNDS\" value=\"4\"/>\n        <env name=\"DB_CONNECTION\" value=\"sqlite\"/>\n        <env name=\"DB_DATABASE\" value=\":memory:\"/>\n    </php>\n</phpunit>\n"
  },
  {
    "path": "src/Commands/AssignAdminPrivileges.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse Spatie\\Permission\\Models\\Role;\nuse RomegaDigital\\Multitenancy\\Multitenancy;\nuse Spatie\\Permission\\Exceptions\\RoleDoesNotExist;\nuse RomegaDigital\\Multitenancy\\Exceptions\\TenantDoesNotExist;\n\nclass AssignAdminPrivileges extends Command\n{\n    /**\n     * The name and signature of the console command.\n     *\n     * @var string\n     */\n    protected $signature = 'multitenancy:super-admin\n                                {identifier : Unique property identifying the user}\n                                {--M|model=\\App\\Models\\User : Model to query the user}\n                                {--C|column=email : Property column name}';\n\n    /**\n     * The console command description.\n     *\n     * @var string\n     */\n    protected $description = 'Give a user super-admin rights and add him to the `admin` tenant';\n\n    /**\n     * Multitenancy Service Class.\n     *\n     * @var RomegaDigital\\Multitenancy\\Multitenancy\n     */\n    protected $multitenancy;\n\n    /**\n     * Create a new command instance.\n     *\n     * @return void\n     */\n    public function __construct(Multitenancy $multitenancy)\n    {\n        parent::__construct();\n\n        $this->multitenancy = $multitenancy;\n    }\n\n    /**\n     * Execute the console command.\n     *\n     * @return mixed\n     */\n    public function handle()\n    {\n        $column = $this->option('column');\n        $userModel = $this->option('model');\n        $identifier = $this->argument('identifier');\n\n        if (!class_exists($userModel)) {\n            return $this->error('User model ' . $userModel . ' can not be found!');\n        }\n\n        if (!$user = $this->getUser($userModel, $column, $identifier)) {\n            return 0;\n        }\n\n        if (!$adminRole = $this->getAdminRole()) {\n            return 0;\n        }\n\n        if (!$adminTenant = $this->getAdminTenant()) {\n            return 0;\n        }\n\n        $user->assignRole($adminRole);\n        $user->tenants()->save($adminTenant);\n\n        $this->info('User with ' . $column . ' ' . $user->{$column} . ' granted Super-Administration rights.');\n\n        return 1;\n    }\n\n    /**\n     * Get user model data.\n     *\n     * @param string $userModel\n     * @param string $column\n     * @param string $identifier\n     *\n     * @return Illuminate\\Database\\Eloquent\\Model\n     */\n    protected function getUser($userModel, $column, $identifier)\n    {\n        if (!$user = $userModel::where($column, $identifier)->first()) {\n            return $this->modelNotFound('User', $column, $identifier);\n        }\n\n        return $user;\n    }\n\n    /**\n     * Get admin role.\n     *\n     * @return Spatie\\Permission\\Contracts\\Role\n     */\n    protected function getAdminRole()\n    {\n        try {\n            return Role::findByName(config('multitenancy.roles.super_admin'));\n        } catch (RoleDoesNotExist $exception) {\n            return $this->cancel('Role', 'name', config('multitenancy.roles.super_admin'));\n        }\n    }\n\n    /**\n     * Get admin tenant.\n     *\n     * @return RomegaDigital\\Multitenancy\\Contracts\\Tenant\n     */\n    protected function getAdminTenant()\n    {\n        try {\n            return $this->multitenancy->getTenantClass()::findByDomain('admin');\n        } catch (TenantDoesNotExist $exception) {\n            return $this->cancel('Tenant', 'domain', 'admin');\n        }\n    }\n\n    /**\n     * Cancel the command due to errors.\n     *\n     * @param Illuminate\\Database\\Eloquent\\Model $model\n     * @param string                             $column\n     * @param string                             $identifier\n     *\n     * @return bool\n     */\n    protected function cancel($model, $column, $identifier)\n    {\n        $this->modelNotFound($model, $column, $identifier);\n        $this->line('');\n        $this->alert('Did you already run `multitenancy:install` command?');\n\n        return false;\n    }\n\n    /**\n     * Write an error for a model which can not be found.\n     *\n     * @param Illuminate\\Database\\Eloquent\\Model $model\n     * @param string                             $column\n     * @param string                             $identifier\n     *\n     * @return void\n     */\n    protected function modelNotFound($model, $column, $identifier)\n    {\n        $this->error(\"$model with $column `$identifier` can not be found!\");\n    }\n}\n"
  },
  {
    "path": "src/Commands/InstallCommand.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Commands;\n\nuse Illuminate\\Console\\Command;\nuse RomegaDigital\\Multitenancy\\Multitenancy;\n\nclass InstallCommand extends Command\n{\n    /**\n     * The name and signature of the console command.\n     *\n     * @var string\n     */\n    protected $signature = 'multitenancy:install\n                                {--M|migrations= : Run migrations}\n                                {--R|roles= : Add super admin role}\n                                {--T|tenant= : Add admin tenant}';\n\n    /**\n     * The console command description.\n     *\n     * @var string\n     */\n    protected $description = 'Perform all steps necessary to setup package quickly';\n\n    /**\n     * Multitenancy Service Class.\n     *\n     * @var RomegaDigital\\Multitenancy\\Multitenancy\n     */\n    protected $multitenancy;\n\n    /**\n     * Create a new command instance.\n     *\n     * @return void\n     */\n    public function __construct(Multitenancy $multitenancy)\n    {\n        parent::__construct();\n\n        $this->multitenancy = $multitenancy;\n    }\n\n    /**\n     * Execute the console command.\n     *\n     * @return mixed\n     */\n    public function handle()\n    {\n        $this->option('migrations') ?? $this->handleMigrations();\n        $this->option('roles') ?? $this->addSuperAdminRole();\n        $this->option('tenant') ?? $this->addAdminTenant();\n\n        return 1;\n    }\n\n    /**\n     * Publishes and migrates required migrations.\n     *\n     * @return void\n     */\n    protected function handleMigrations()\n    {\n        $this->info('Publishing required migrations...');\n\n        $this->callSilent('vendor:publish', [\n            '--provider' => 'Spatie\\Permission\\PermissionServiceProvider',\n            '--tag'      => ['permission-migrations'],\n        ]);\n\n        $this->callSilent('vendor:publish', [\n            '--provider' => 'RomegaDigital\\Multitenancy\\MultitenancyServiceProvider',\n            '--tag'      => ['migrations'],\n        ]);\n\n        $this->info('Migrations published!');\n\n        $this->line('');\n        $this->call('migrate');\n        $this->line('');\n    }\n\n    /**\n     * Creates a super admin role and 'access admin'\n     * permission.\n     *\n     * @return void\n     */\n    protected function addSuperAdminRole()\n    {\n        $this->info('Adding `Super Administrator` Role...');\n\n        $this->call('permission:create-role', [\n            'name'        => config('multitenancy.roles.super_admin'),\n            'permissions' => 'access admin',\n        ]);\n\n        $this->line('');\n    }\n\n    /**\n     * Creates the admin tenant model.\n     *\n     * @return void\n     */\n    protected function addAdminTenant()\n    {\n        $this->info('Adding `admin` domain...');\n\n        $this->multitenancy->getTenantClass()::updateOrCreate([\n            'name'   => 'Admin Portal',\n            'domain' => 'admin',\n        ]);\n\n        $this->info('Admin domain added successfully!');\n    }\n}\n"
  },
  {
    "path": "src/Commands/MigrationMakeCommand.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Commands;\n\nuse Illuminate\\Console\\GeneratorCommand;\nuse Illuminate\\Filesystem\\Filesystem;\nuse Illuminate\\Support\\Composer;\nuse Illuminate\\Support\\Str;\n\nclass MigrationMakeCommand extends GeneratorCommand\n{\n    /**\n     * The name and signature of the console command.\n     *\n     * @var string\n     */\n    protected $signature = 'multitenancy:migration {name : The name of the table}';\n\n    /**\n     * The console command description.\n     *\n     * @var string\n     */\n    protected $description = 'Create a new tenant migration file';\n\n    /**\n     * The type of class being generated.\n     *\n     * @var string\n     */\n    protected $type = 'Multitenancy migration';\n\n    /**\n     * The Composer instance.\n     *\n     * @var \\Illuminate\\Support\\Composer\n     */\n    protected $composer;\n\n    /**\n     * Create a new command instance.\n     *\n     * @param \\Illuminate\\Filesystem\\Filesystem $files\n     * @param \\Illuminate\\Support\\Composer      $composer\n     *\n     * @return void\n     */\n    public function __construct(Filesystem $files, Composer $composer)\n    {\n        parent::__construct($files);\n\n        $this->composer = $composer;\n    }\n\n    /**\n     * Execute the console command.\n     *\n     * @return mixed\n     */\n    public function handle()\n    {\n        parent::handle();\n\n        $this->composer->dumpAutoloads();\n\n        $this->info('Multitenancy migration created successfully.');\n\n        return 1;\n    }\n\n    /**\n     * Get the stub file for the generator.\n     *\n     * @return string\n     */\n    protected function getStub()\n    {\n        return __DIR__.'/stubs/add_tenancy_to_table.stub';\n    }\n\n    /**\n     * Build the class with the given name.\n     *\n     * @param string $name\n     *\n     * @throws \\Illuminate\\Contracts\\Filesystem\\FileNotFoundException\n     *\n     * @return string\n     */\n    protected function buildClass($name)\n    {\n        $stub = parent::buildClass($name);\n\n        return str_replace(\n            ['DummyTable', 'DummyTenantTable'],\n            [lcfirst($this->getNameInput()), config('multitenancy.table_names.tenants')],\n            $stub\n        );\n    }\n\n    /**\n     * Replace the class name for the given stub.\n     *\n     * @param string $stub\n     * @param string $name\n     *\n     * @return string\n     */\n    protected function replaceClass($stub, $name)\n    {\n        $class = 'AddTenantIDColumnTo'.Str::studly($this->getNameInput()).'Table';\n\n        return str_replace('DummyClass', $class, $stub);\n    }\n\n    /**\n     * Get the destination class path.\n     *\n     * @param string $name\n     *\n     * @return string\n     */\n    protected function getPath($name)\n    {\n        $timestamp = date('Y_m_d_His');\n        $table = lcfirst($this->getNameInput());\n\n        return $this->laravel->databasePath().\"/migrations/{$timestamp}_add_tenant_id_column_to_{$table}_table.php\";\n    }\n}\n"
  },
  {
    "path": "src/Commands/stubs/add_tenancy_to_table.stub",
    "content": "<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migrations\\Migration;\n\nclass DummyClass extends Migration\n{\n    /**\n     * Run the migrations.\n     *\n     * @return void\n     */\n    public function up()\n    {\n        Schema::table(\"DummyTable\", function (Blueprint $table) {\n            $table->unsignedBigInteger('tenant_id')->nullable();\n            $table->foreign('tenant_id')\n                ->references('id')\n                ->on(\"DummyTenantTable\")\n                ->onDelete('cascade');\n        });\n    }\n\n    /**\n     * Reverse the migrations.\n     *\n     * @return void\n     */\n    public function down()\n    {\n        Schema::table(\"DummyTable\", function (Blueprint $table) {\n            $table->dropColumn('tenant_id');\n        });\n    }\n}\n"
  },
  {
    "path": "src/Contracts/Tenant.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Contracts;\n\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\n\ninterface Tenant\n{\n    /**\n     * A Tenant belongs to many users.\n     *\n     * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany\n     */\n    public function users(): BelongsToMany;\n\n    /**\n     * Find a Tenant by its domain.\n     *\n     * @param string $domain\n     *\n     * @throws \\RomegaDigital\\Multitenancy\\Exceptions\\TenantDoesNotExist\n     *\n     * @return Tenant\n     */\n    public static function findByDomain(string $domain): self;\n}\n"
  },
  {
    "path": "src/Exceptions/TenantDoesNotExist.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Exceptions;\n\nuse InvalidArgumentException;\n\nclass TenantDoesNotExist extends InvalidArgumentException\n{\n    /**\n     * A Tenant does not exist at the supplied domain.\n     *\n     * @param string $domain\n     *\n     * @return static\n     */\n    public static function forDomain(string $domain): self\n    {\n        return new static(\"There is no tenant at domain `{$domain}`.\");\n    }\n}\n"
  },
  {
    "path": "src/Exceptions/UnauthorizedException.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Exceptions;\n\nuse Symfony\\Component\\HttpKernel\\Exception\\HttpException;\n\nclass UnauthorizedException extends HttpException\n{\n    /**\n     * A user does not have valid permissions to access\n     * the supplied domain.\n     *\n     * @param string $domain\n     *\n     * @return static\n     */\n    public static function forDomain(string $domain): self\n    {\n        $message = \"The authenticated user does not have access to domain `{$domain}`.\";\n\n        $exception = new static(403, $message, null, []);\n\n        return $exception;\n    }\n\n    /**\n     * A user is not logged in.\n     *\n     * @return static\n     */\n    public static function notLoggedIn(): self\n    {\n        return new static(403, 'User is not logged in.', null, []);\n    }\n}\n"
  },
  {
    "path": "src/Middleware/GuestTenantMiddleware.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Middleware;\n\nuse Closure;\nuse RomegaDigital\\Multitenancy\\Multitenancy;\n\nclass GuestTenantMiddleware\n{\n    /**\n     * @var RomegaDigital\\Multitenancy\\Multitenancy\n     */\n    protected $multitenancy;\n\n    /**\n     * Create new TenantMiddleware instance.\n     *\n     * @param Illuminate\\Contracts\\Auth\\Factory $auth\n     * @param RomegaDigital\\Multitenancy\\Multitenancy $multitenancy\n     */\n    public function __construct(Multitenancy $multitenancy)\n    {\n        $this->multitenancy = $multitenancy;\n    }\n\n    /**\n     * Handle an incoming request.\n     *\n     * @param \\Illuminate\\Http\\Request $request\n     * @param \\Closure                 $next\n     * @param string|null              $guard\n     *\n     * @return mixed\n     */\n    public function handle($request, Closure $next)\n    {\n        $tenant = $this->multitenancy->receiveTenantFromRequest();\n\n        $this->multitenancy->setTenant($tenant)->applyTenantScopeToDeferredModels();\n\n        return $next($request);\n    }\n}\n"
  },
  {
    "path": "src/Middleware/TenantMiddleware.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Middleware;\n\nuse Closure;\nuse RomegaDigital\\Multitenancy\\Multitenancy;\nuse Illuminate\\Contracts\\Auth\\Factory as Auth;\nuse RomegaDigital\\Multitenancy\\Contracts\\Tenant;\nuse Illuminate\\Auth\\Middleware\\Authenticate as Middleware;\nuse RomegaDigital\\Multitenancy\\Exceptions\\UnauthorizedException;\n\nclass TenantMiddleware extends Middleware\n{\n    /**\n     * @var RomegaDigital\\Multitenancy\\Multitenancy\n     */\n    protected $multitenancy;\n\n    /**\n     * Create new TenantMiddleware instance.\n     *\n     * @param Illuminate\\Contracts\\Auth\\Factory       $auth\n     * @param RomegaDigital\\Multitenancy\\Multitenancy $multitenancy\n     */\n    public function __construct(Auth $auth, Multitenancy $multitenancy)\n    {\n        parent::__construct($auth);\n\n        $this->multitenancy = $multitenancy;\n    }\n\n    /**\n     * Get the path the user should be redirected to when they are not authenticated.\n     *\n     * @param \\Illuminate\\Http\\Request $request\n     *\n     * @return string\n     */\n    protected function redirectTo($request)\n    {\n        if (! $request->expectsJson()) {\n            return route(config('multitenancy.redirect_route'));\n        }\n    }\n\n    /**\n     * Handle an incoming request.\n     *\n     * @param \\Illuminate\\Http\\Request $request\n     * @param \\Closure                 $next\n     * @param string[]                 ...$guards\n     *\n     * @throws \\RomegaDigital\\Multitenancy\\Exceptions\\UnauthorizedException|\\Illuminate\\Auth\\AuthenticationException\n     *\n     * @return mixed\n     */\n    public function handle($request, Closure $next, ...$guards)\n    {\n        $this->authenticate($request, $guards);\n\n        $tenant = $this->multitenancy->receiveTenantFromRequest();\n\n        if (! $this->authorizedToAccessTenant($tenant)) {\n            throw UnauthorizedException::forDomain($tenant->domain);\n        }\n\n        $this->multitenancy->setTenant($tenant)->applyTenantScopeToDeferredModels();\n\n        $request->merge([Multitenancy::TENANT_SET_HEADER => true]);\n\n        return $next($request);\n    }\n\n    /**\n     * Check if user is authorized to access tenant's domain.\n     *\n     * @param \\RomegaDigital\\Multitenancy\\Contracts\\Tenant $tenant\n     *\n     * @return bool\n     */\n    protected function authorizedToAccessTenant(Tenant $tenant)\n    {\n        return $tenant && $tenant->users->contains(auth()->user()->id);\n    }\n}\n"
  },
  {
    "path": "src/Models/Tenant.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\nuse RomegaDigital\\Multitenancy\\Exceptions\\TenantDoesNotExist;\nuse RomegaDigital\\Multitenancy\\Contracts\\Tenant as TenantContract;\n\nclass Tenant extends Model implements TenantContract\n{\n    use SoftDeletes;\n    /**\n     * The attributes that are mass assignable.\n     *\n     * @var array\n     */\n    protected $fillable = [\n        'name',\n        'domain',\n    ];\n\n    /**\n     * Create new Tenant instance.\n     *\n     * @param array $attributes\n     */\n    public function __construct(array $attributes = [])\n    {\n        parent::__construct($attributes);\n\n        $this->setTable(config('multitenancy.table_names.tenants'));\n    }\n\n    /**\n     * A Tenant belongs to many users.\n     *\n     * @return \\Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany\n     */\n    public function users(): BelongsToMany\n    {\n        return $this->belongsToMany(config('multitenancy.user_model'))\n            ->withTimestamps();\n    }\n\n    /**\n     * Find a Tenant by its domain.\n     *\n     * @param string $domain\n     *\n     * @throws \\RomegaDigital\\Multitenancy\\Exceptions\\TenantDoesNotExist\n     *\n     * @return \\RomegaDigital\\Multitenancy\\Contracts\\Tenant\n     */\n    public static function findByDomain(string $domain): TenantContract\n    {\n        $tenant = static::where(['domain' => $domain])->first();\n\n        if (! $tenant) {\n            throw TenantDoesNotExist::forDomain($domain);\n        }\n\n        return $tenant;\n    }\n}\n"
  },
  {
    "path": "src/Multitenancy.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse RomegaDigital\\Multitenancy\\Contracts\\Tenant;\n\nclass Multitenancy\n{\n    const TENANT_SET_HEADER = 'X-Multitenancy-Tenant';\n\n    /**\n     * The tenant model as defined in the config file.\n     *\n     * @var string\n     */\n    protected $tenantClass;\n\n    /**\n     * The current tenant model.\n     *\n     * @var RomegaDigital\\Multitenancy\\Contracts\\Tenant\n     */\n    protected $tenant = null;\n\n    /**\n     * Models that need scopes before the app fully boots\n     * they will be processed at a later time.\n     *\n     * @var collect\n     */\n    protected $deferredModels;\n\n    /**\n     * Multitenancy constructor.\n     */\n    public function __construct()\n    {\n        $this->tenantClass = config('multitenancy.tenant_model');\n        $this->deferredModels = collect();\n    }\n\n    /**\n     * Sets the Tenant to a Tenant Model.\n     *\n     * @param RomegaDigital\\Multitenancy\\Contracts\\Tenant $tenant\n     *\n     * @return $this\n     */\n    public function setTenant(Tenant $tenant)\n    {\n        $this->tenant = $tenant;\n\n        return $this;\n    }\n\n    /**\n     * Returns the current Tenant.\n     *\n     * @return \\RomegaDigital\\Multitenancy\\Contracts\\Tenant\n     */\n    public function currentTenant(): Tenant\n    {\n        return $this->tenant ?? $this->receiveTenantFromRequest();\n    }\n\n    /**\n     * Applies applicable tenant scopes to model or if not booted yet\n     * store for deferment.\n     *\n     * @param Illuminate\\Database\\Eloquent\\Model $model\n     *\n     * @return void|null\n     */\n    public function applyTenantScope(Model $model)\n    {\n        if (is_null($this->tenant)) {\n            $this->deferredModels->push($model);\n\n            return;\n        }\n\n        if ('admin' === $this->tenant->domain) {\n            return;\n        }\n\n        $model->addGlobalScope('tenant', function (Builder $builder) use ($model) {\n            $builder->where($model->qualifyColumn('tenant_id'), '=', $this->tenant->id);\n        });\n    }\n\n    /**\n     * Applies applicable tenant id to model on create.\n     *\n     * @param Illuminate\\Database\\Eloquent\\Model $model\n     *\n     * @return void|null\n     */\n    public function newModel(Model $model)\n    {\n        if (is_null($this->tenant)) {\n            $this->deferredModels->push($model);\n\n            return;\n        }\n\n        if (! isset($model->tenant_id)) {\n            $model->setAttribute('tenant_id', $this->tenant->id);\n        }\n    }\n\n    /**\n     * Applies applicable tenant scope to deferred model booted\n     * before tenants setup.\n     */\n    public function applyTenantScopeToDeferredModels()\n    {\n        $this->deferredModels->each(function ($model) {\n            $this->applyTenantScope($model);\n        });\n\n        $this->deferredModels = collect();\n    }\n\n    /**\n     * Get an instance of the tenant class.\n     *\n     * @return \\RomegaDigital\\Multitenancy\\Contracts\\Tenant\n     */\n    public function getTenantClass(): Tenant\n    {\n        return app($this->tenantClass);\n    }\n\n    /**\n     * Determines how best to process the URL based\n     * on config and then returns the appropriate\n     * subdomain text.\n     *\n     * @return string\n     */\n    public function getCurrentSubDomain(): string\n    {\n        $baseURL = config('multitenancy.base_url');\n\n        if (null != $baseURL) {\n            return $this->getSubDomainBasedOnBaseURL($baseURL);\n        } else {\n            return $this->getSubDomainBasedOnHTTPHost();\n        }\n    }\n\n    /**\n     * Parses the request to pull out the first element separated\n     * by `.` in the $_SERVER['HTTP_HOST'].\n     *\n     * ex:\n     * test.domain.com returns test\n     * test2.test.domain.com returns test2\n     *\n     * @return string\n     */\n    protected function getSubDomainBasedOnHTTPHost(): string\n    {\n        $currentDomain = app('request')->getHost();\n\n        // Get rid of the TLD and root domain\n        // ex: masterdomain.test.example.com returns\n        // [ masterdomain, test ]\n        $subdomains = explode('.', $currentDomain, -2);\n\n        // Combine multiple level of domains into 1 string\n        // ex: back to masterdomain.test\n        $subdomain = implode('.', $subdomains);\n\n        return $subdomain;\n    }\n\n    /**\n     * Parses the request and removes the portion of the URL\n     * that matches the Base URL as defined in the config file.\n     *\n     * ex:\n     * baseURL = app.domain.com\n     * test2.app.domain.com returns test2\n     *\n     * @return string\n     */\n    protected function getSubDomainBasedOnBaseURL(string $baseURL): string\n    {\n        $currentDomain = app('request')->getHost();\n\n        //Remove the base domain from the currentDomain string\n        $subdomain = str_replace($baseURL, '', $currentDomain);\n\n        // If the last element is a period, remove it\n        // Necessary to run this check, incase we're\n        // processing the base domain.\n        if ('.' == substr($subdomain, -1)) {\n            $subdomain = substr($subdomain, 0, -1);\n        }\n\n        return $subdomain;\n    }\n\n    /**\n     * Returns tenant from request subdomain.\n     *\n     * @return \\RomegaDigital\\Multitenancy\\Contracts\\Tenant\n     */\n    public function receiveTenantFromRequest()\n    {\n        $domain = $this->getCurrentSubDomain();\n\n        return $this->getTenantClass()::findByDomain($domain);\n    }\n}\n"
  },
  {
    "path": "src/MultitenancyFacade.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\nclass MultitenancyFacade extends Facade\n{\n    /**\n     * Access the facade.\n     *\n     * @return string\n     */\n    protected static function getFacadeAccessor()\n    {\n        return 'multitenancy';\n    }\n}\n"
  },
  {
    "path": "src/MultitenancyServiceProvider.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy;\n\nuse Illuminate\\Support\\Collection;\nuse Illuminate\\Support\\Facades\\Gate;\nuse Illuminate\\Filesystem\\Filesystem;\nuse Illuminate\\Support\\ServiceProvider;\nuse RomegaDigital\\Multitenancy\\Commands\\InstallCommand;\nuse RomegaDigital\\Multitenancy\\Commands\\MigrationMakeCommand;\nuse RomegaDigital\\Multitenancy\\Commands\\AssignAdminPrivileges;\nuse RomegaDigital\\Multitenancy\\Contracts\\Tenant as TenantContract;\n\nclass MultitenancyServiceProvider extends ServiceProvider\n{\n    /**\n     * Bootstrap the package services.\n     *\n     * @param Illuminate\\Filesystem\\Filesystem $filesystem\n     */\n    public function boot(Filesystem $filesystem)\n    {\n        $this->loadMigrationsFrom(realpath(__DIR__ . '/../migrations'));\n\n        if ($this->app->runningInConsole()) {\n            $this->registerPublishing($filesystem);\n        }\n\n        $this->registerCommands();\n        $this->registerModelBindings();\n\n        Gate::before(function ($user, $ability) {\n            if ($user->hasRole(config('multitenancy.roles.super_admin'))\n                && 'admin' === app('multitenancy')->getCurrentSubDomain()) {\n                return true;\n            }\n        });\n    }\n\n    /**\n     * Register the application services.\n     */\n    public function register()\n    {\n        $this->mergeConfigFrom(\n            __DIR__ . '/../config/multitenancy.php',\n            'multitenancy'\n        );\n\n        $this->app->singleton(Multitenancy::class, function () {\n            return new Multitenancy();\n        });\n\n        $this->app->alias(Multitenancy::class, 'multitenancy');\n    }\n\n    /**\n     * Register the package's publishable resources.\n     *\n     * @param Illuminate\\Filesystem\\Filesystem $filesystem\n     */\n    protected function registerPublishing(Filesystem $filesystem)\n    {\n        $this->publishes([\n            __DIR__ . '/../migrations/create_'.config('multitenancy.table_names.tenants').'_table.php.stub' => $this->getMigrationFileName($filesystem),\n        ], 'migrations');\n\n        $this->publishes([\n            __DIR__ . '/../config/multitenancy.php' => config_path('multitenancy.php'),\n        ], 'config');\n    }\n\n    /**\n     * Registers all commands within the package.\n     */\n    protected function registerCommands()\n    {\n        $this->commands([\n            InstallCommand::class,\n            MigrationMakeCommand::class,\n            AssignAdminPrivileges::class,\n        ]);\n    }\n\n    /**\n     * Register model bindings.\n     */\n    protected function registerModelBindings()\n    {\n        $this->app->bind(TenantContract::class, $this->app->config['multitenancy.tenant_model']);\n    }\n\n    /**\n     * Returns existing migration file if found, else uses the current timestamp.\n     *\n     * @param Illuminate\\Filesystem\\Filesystem $filesystem\n     *\n     * @return string\n     */\n    protected function getMigrationFileName(Filesystem $filesystem): string\n    {\n        $timestamp = date('Y_m_d_His');\n\n        return Collection::make($this->app->databasePath() . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR)\n            ->flatMap(function ($path) use ($filesystem) {\n                return $filesystem->glob($path . '*_create_tenants_table.php');\n            })->push($this->app->databasePath() . \"/migrations/{$timestamp}_create_tenants_table.php\")\n            ->first();\n    }\n}\n"
  },
  {
    "path": "src/Traits/BelongsToTenant.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Traits;\n\nuse RomegaDigital\\Multitenancy\\Multitenancy;\n\ntrait BelongsToTenant\n{\n    /**\n     * The \"booting\" method of the tenant model.\n     * This defines the query scopes and creation scopes.\n     */\n    public static function bootBelongsToTenant()\n    {\n        resolve(Multitenancy::class)->applyTenantScope(new static());\n\n        static::creating(function ($model) {\n            resolve(Multitenancy::class)->newModel($model);\n        });\n    }\n\n    /**\n     * The model belongs to a tenant.\n     *\n     * @return Illuminate\\Database\\Eloquent\\Relations\\BelongsTo\n     */\n    public function tenant()\n    {\n        return $this->belongsTo(config('multitenancy.tenant_model'));\n    }\n}\n"
  },
  {
    "path": "src/Traits/HasTenants.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Traits;\n\nuse RomegaDigital\\Multitenancy\\Multitenancy;\n\ntrait HasTenants\n{\n    /**\n     * Add the current tenant to the created user tenants.\n     */\n    public static function bootHasTenants()\n    {\n        static::created(function ($model) {\n            $ignoreTenantOnUserCreation = config('multitenancy.ignore_tenant_on_user_creation');\n\n            if (! request()->has(Multitenancy::TENANT_SET_HEADER) || $ignoreTenantOnUserCreation) {\n                return;\n            }\n\n            $model->tenants()->save(\n                resolve(Multitenancy::class)->currentTenant()\n            );\n        });\n    }\n\n    /**\n     * The model belongs to many tenants.\n     *\n     * @return Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany\n     */\n    public function tenants()\n    {\n        return $this->belongsToMany(config('multitenancy.tenant_model'))\n            ->withTimestamps();\n    }\n}\n"
  },
  {
    "path": "tests/Databases/MigrateDatabaseTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Databases;\n\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\n\nclass MigrateDatabaseTest extends TestCase\n{\n    /** @test */\n    public function it_runs_the_migrations()\n    {\n        $columns = \\Schema::getColumnListing('tenants');\n        $this->assertEquals([\n            'id',\n            'name',\n            'domain',\n            'deleted_at',\n            'created_at',\n            'updated_at',\n        ], $columns);\n\n        $columns = \\Schema::getColumnListing('tenant_user');\n        $this->assertEquals([\n            'id',\n            'tenant_id',\n            'user_id',\n            'created_at',\n            'updated_at',\n            'deleted_at',\n        ], $columns);\n    }\n}\n"
  },
  {
    "path": "tests/Feature/BelongsToTenantTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse RomegaDigital\\Multitenancy\\Models\\Tenant;\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Product;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Controllers\\ProductController;\n\nclass BelongsToTenantTest extends TestCase\n{\n    /**\n     * Define environment setup.\n     *\n     * @param Illuminate\\Foundation\\Application $app\n     */\n    protected function getEnvironmentSetUp($app)\n    {\n        parent::getEnvironmentSetUp($app);\n\n        $app['router']->resource('products', ProductController::class);\n    }\n\n    /**\n     * Turn the given URI into a fully qualified URL.\n     *\n     * @param string $uri\n     *\n     * @return string\n     */\n    protected function prepareUrlForRequest($uri)\n    {\n        $uri = \"http://{$this->testTenant->domain}.localhost.com/{$uri}\";\n\n        return trim($uri, '/');\n    }\n\n    /** @test */\n    public function it_adds_current_tenant_id_to_model_on_create()\n    {\n        $this->actingAs($this->testUser);\n        $this->testTenant->users()->save($this->testUser);\n\n        $response = $this->post('products', [\n            'name' => 'Another Tenants Product',\n        ]);\n        $response->assertStatus(201);\n        $this->assertEquals(Product::first()->tenant_id, $this->testTenant->id);\n    }\n\n    /** @test */\n    public function it_only_retrieves_records_scoped_to_current_subdomain()\n    {\n        $this->actingAs($this->testUser);\n        $this->testTenant->users()->save($this->testUser);\n\n        Product::create([\n            'name' => 'Another Tenants Product',\n            'tenant_id' => Tenant::create([\n                'name' => 'Another Tenant',\n                'domain' => 'anotherdomain',\n            ])->id,\n        ]);\n\n        $response = $this->get('products');\n        $response->assertStatus(200);\n        $this->assertEquals(Product::where('tenant_id', $this->testTenant->id)->get(), $response->getContent());\n    }\n\n    /** @test **/\n    public function it_retrieves_all_records_when_accessing_via_admin_subdomain()\n    {\n        $this->actingAs($this->testUser);\n        $this->testAdminTenant->users()->save($this->testUser);\n        $this->testTenant->domain = $this->testAdminTenant->domain;\n\n        Product::create([\n            'name' => 'Another Tenants Product',\n            'tenant_id' => Tenant::create([\n                'name' => 'Another Tenant',\n                'domain' => 'anotherdomain',\n            ])->id,\n        ]);\n\n        $response = $this->get('products');\n        $response->assertStatus(200);\n        $this->assertEquals(Product::withoutGlobalScopes()->get(), $response->getContent());\n    }\n}\n"
  },
  {
    "path": "tests/Feature/Commands/AssignAdminPrivilegesTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Commands;\n\nuse Spatie\\Permission\\Models\\Role;\nuse RomegaDigital\\Multitenancy\\Models\\Tenant;\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\User;\n\nclass AssignAdminPrivilegesTest extends TestCase\n{\n    /** @test */\n    public function it_throws_an_error_and_exits_if_the_given_user_model_class_is_not_found()\n    {\n        $this->artisan('multitenancy:super-admin', [\n                'identifier' => 'test@user.com',\n            ])\n            ->expectsOutput('User model \\App\\Models\\User can not be found!')\n            ->assertExitCode(0);\n    }\n\n    /** @test */\n    public function it_throws_an_error_and_exits_if_no_user_model_is_found()\n    {\n        $this->artisan('multitenancy:super-admin', [\n                'identifier' => 'fail@user.com',\n                '--model' => config('multitenancy.user_model'),\n            ])\n            ->expectsOutput('User with email `fail@user.com` can not be found!')\n            ->assertExitCode(0);\n    }\n\n    /** @test */\n    public function it_throws_an_error_and_exits_if_no_super_adminitration_role_is_found()\n    {\n        $this->artisan('multitenancy:super-admin', [\n                'identifier' => 'test@user.com',\n                '--model' => config('multitenancy.user_model'),\n            ])\n            ->expectsOutput('Role with name `Super Administrator` can not be found!')\n            ->expectsOutput('*     Did you already run `multitenancy:install` command?     *')\n            ->assertExitCode(0);\n    }\n\n    /** @test */\n    public function it_throws_an_error_and_exits_if_no_admin_tenant_is_found()\n    {\n        Role::create(['name' => 'Super Administrator']);\n\n        $tenant = Tenant::findByDomain('admin');\n        $tenant->domain = 'testadmin';\n        $tenant->save();\n\n        $this->artisan('multitenancy:super-admin', [\n            'identifier' => 'test@user.com',\n            '--model' => config('multitenancy.user_model'),\n        ])\n        ->expectsOutput('Tenant with domain `admin` can not be found!')\n        ->expectsOutput('*     Did you already run `multitenancy:install` command?     *')\n        ->assertExitCode(0);\n    }\n\n    /** @test */\n    public function it_assigns_super_administrator_role_and_admin_tenant_to_given_user()\n    {\n        Role::create(['name' => 'Super Administrator']);\n\n        $this->artisan('multitenancy:super-admin', [\n                'identifier' => 'test@user.com',\n                '--model' => config('multitenancy.user_model'),\n            ])\n            ->expectsOutput('User with email test@user.com granted Super-Administration rights.')\n            ->assertExitCode(1);\n\n        $user = User::whereEmail('test@user.com')->first();\n        $this->assertTrue($user->hasRole('Super Administrator'));\n    }\n}\n"
  },
  {
    "path": "tests/Feature/Commands/InstallCommandTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Commands;\n\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\n\nclass InstallCommandTest extends TestCase\n{\n    public $setupTestDatabase = false;\n\n    /** @test */\n    public function it_published_and_migrates_required_migrations_and_creates_admin_role_and_tenant()\n    {\n        $this->artisan('multitenancy:install')\n            ->expectsOutput('Publishing required migrations...')\n            ->expectsOutput('Migrations published!')\n            ->expectsOutput('Adding `Super Administrator` Role...')\n            ->expectsOutput('Role `Super Administrator` created')\n            ->expectsOutput('Adding `admin` domain...')\n            ->expectsOutput('Admin domain added successfully!')\n            ->assertExitCode(1);\n    }\n}\n"
  },
  {
    "path": "tests/Feature/Commands/MigrationMakeCommandTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Commands;\n\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\n\nclass MigrationMakeCommandTest extends TestCase\n{\n    public $setupTestDatabase = false;\n\n    /** @test */\n    public function it_adds_a_new_migration_with_tenant_id_to_the_specified_table()\n    {\n        $this->mock(\\Illuminate\\Filesystem\\Filesystem::class)\n            ->makePartial()\n            ->shouldReceive('put')\n            ->once();\n\n        $this->artisan('multitenancy:migration', ['name' => 'testproducts'])\n            ->expectsOutput('Multitenancy migration created successfully.')\n            ->assertExitCode(1);\n    }\n\n    /** @test **/\n    public function it_can_handle_multiword_names()\n    {\n        $this->mock(\\Illuminate\\Filesystem\\Filesystem::class)\n            ->makePartial()\n            ->shouldReceive('put')\n            ->with(\\Mockery::any(), \\Mockery::pattern('/AddTenantIDColumnToTestNameTable/'))\n            ->once();\n\n        $this->artisan('multitenancy:migration', ['name' => 'test_name']);\n    }\n}\n"
  },
  {
    "path": "tests/Feature/GateTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse Spatie\\Permission\\Models\\Role;\nuse Illuminate\\Support\\Facades\\Gate;\nuse RomegaDigital\\Multitenancy\\Models\\Tenant;\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Product;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Policies\\ProductPolicy;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Controllers\\ProductController;\n\nclass GateTest extends TestCase\n{\n    /**\n     * Define environment setup.\n     *\n     * @param Illuminate\\Foundation\\Application $app\n     */\n    protected function getEnvironmentSetUp($app)\n    {\n        parent::getEnvironmentSetUp($app);\n\n        $app['router']->resource('products', ProductController::class);\n\n        Gate::policy(Product::class, ProductPolicy::class);\n    }\n\n    /**\n     * Turn the given URI into a fully qualified URL.\n     *\n     * @param string $uri\n     *\n     * @return string\n     */\n    protected function prepareUrlForRequest($uri)\n    {\n        $uri = \"http://admin.localhost.com/{$uri}\";\n\n        return trim($uri, '/');\n    }\n\n    /** @test **/\n    public function it_does_not_allow_regular_user()\n    {\n        $this->actingAs($this->testUser);\n        $this->testAdminTenant->users()->save($this->testUser);\n\n        $product = Product::create([\n            'name' => 'Another Tenants Product',\n            'tenant_id' => Tenant::create([\n                'name' => 'Another Tenant',\n                'domain' => 'anotherdomain',\n            ])->id,\n        ]);\n\n        $response = $this->get('products/' . $product->id);\n\n        $response->assertForbidden();\n    }\n\n    /** @test **/\n    public function it_does_not_allow_super_administrator_not_tied_to_admin_subdomain()\n    {\n        Role::create(['name' => 'Super Administrator']);\n        $this->actingAs($this->testUser);\n        $this->testUser->assignRole('Super Administrator');\n\n        $product = Product::create([\n            'name' => 'Another Tenants Product',\n            'tenant_id' => Tenant::create([\n                'name' => 'Another Tenant',\n                'domain' => 'anotherdomain',\n            ])->id,\n        ]);\n\n        $response = $this->get('products/' . $product->id);\n\n        $response->assertForbidden();\n    }\n\n    /** @test **/\n    public function it_does_allow_super_administrator_tied_to_domain()\n    {\n        Role::create(['name' => 'Super Administrator']);\n        $this->actingAs($this->testUser);\n        $this->testUser->assignRole('Super Administrator');\n        $this->testAdminTenant->users()->save($this->testUser);\n\n        $product = Product::create([\n            'name' => 'Another Tenants Product',\n            'tenant_id' => Tenant::create([\n                'name' => 'Another Tenant',\n                'domain' => 'anotherdomain',\n            ])->id,\n        ]);\n\n        $response = $this->get('products/' . $product->id);\n\n        $response->assertOK();\n    }\n}\n"
  },
  {
    "path": "tests/Feature/HasTenantTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\nuse RomegaDigital\\Multitenancy\\Contracts\\Tenant;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\User;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Controllers\\UserController;\n\nclass HasTenantTest extends TestCase\n{\n    /**\n     * Define environment setup.\n     *\n     * @param Illuminate\\Foundation\\Application $app\n     */\n    protected function getEnvironmentSetUp($app)\n    {\n        parent::getEnvironmentSetUp($app);\n\n        $app['router']->resource('users', UserController::class);\n    }\n\n    /**\n     * Turn the given URI into a fully qualified URL.\n     *\n     * @param string $uri\n     *\n     * @return string\n     */\n    protected function prepareUrlForRequest($uri)\n    {\n        $uri = \"http://{$this->testTenant->domain}.localhost.com/{$uri}\";\n\n        return trim($uri, '/');\n    }\n\n    /** @test */\n    public function it_adds_current_tenant_id_to_user_model_on_create()\n    {\n        $this->actingAs($this->testUser);\n        $this->testTenant->users()->save($this->testUser);\n\n        $this->post('users', [\n                'email' => $email = 'another@user.com',\n                'name' => 'UserName',\n                'password' => 'PassWord',\n            ])\n            ->assertStatus(201);\n\n        $this->assertContains($this->testTenant->id, User::whereEmail($email)->first()->tenants->pluck('id'));\n    }\n\n    /** @test */\n    public function it_does_not_add_a_tenant_if_the_the_ignore_tenant_on_user_creation_is_set()\n    {\n        config(['multitenancy.ignore_tenant_on_user_creation' => true]);\n        $this->testTenant->users()->save($this->testUser);\n        $otherTenant = resolve(Tenant::class)->create([\n            'name' => 'Other',\n            'domain' => 'other',\n        ]);\n\n        $this->actingAs($this->testUser)\n            ->post('users', [\n                'email' => $email = 'with@tenant.com',\n                'name' => 'UserName',\n                'password' => 'PassWord',\n                'tenant' => $otherTenant,\n            ])\n            ->assertStatus(201);\n\n        $this->assertContains($otherTenant->id, $tenantIds = User::whereEmail($email)->first()->tenants->pluck('id'));\n        $this->assertNotContains($this->testTenant->id, $tenantIds);\n    }\n}\n"
  },
  {
    "path": "tests/Feature/Middleware/GuestMiddlewareTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Middleware;\n\nuse Illuminate\\Http\\Response;\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\nuse RomegaDigital\\Multitenancy\\Exceptions\\TenantDoesNotExist;\nuse RomegaDigital\\Multitenancy\\Middleware\\GuestTenantMiddleware;\n\nclass GuestMiddlewareTest extends TestCase\n{\n    protected $tenantMiddleware;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->tenantMiddleware = new GuestTenantMiddleware(app('multitenancy'));\n    }\n\n    protected function buildRequest($domain)\n    {\n        app('request')->headers->set('HOST', $domain . '.example.com');\n\n        return $this->tenantMiddleware->handle(app('request'), function () {\n            return (new Response())->setContent('<html></html>');\n        });\n    }\n\n    /** @test */\n    public function it_throws_error_if_domain_not_found()\n    {\n        try {\n            $this->buildRequest('testdomain');\n            $this->fail('Expected exception not thrown');\n        } catch (TenantDoesNotExist $e) { //Not catching a generic Exception or the fail function is also catched\n            $this->assertEquals('There is no tenant at domain `testdomain`.', $e->getMessage());\n        }\n    }\n\n    /** @test **/\n    public function it_allows_guest_users_to_access_tenant_scoped_requests()\n    {\n        $this->assertEquals(\n            $this->buildRequest($this->testTenant->domain)->getStatusCode(),\n            200\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Feature/Middleware/TenantMiddlewareTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature\\Middleware;\n\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Auth\\AuthenticationException;\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\nuse RomegaDigital\\Multitenancy\\Middleware\\TenantMiddleware;\nuse RomegaDigital\\Multitenancy\\Exceptions\\TenantDoesNotExist;\nuse RomegaDigital\\Multitenancy\\Exceptions\\UnauthorizedException;\n\nclass TenantMiddlewareTest extends TestCase\n{\n    protected $tenantMiddleware;\n\n    /**\n     * Define environment setup.\n     *\n     * @param Illuminate\\Foundation\\Application $app\n     *\n     * @return void\n     */\n    protected function getEnvironmentSetUp($app)\n    {\n        parent::getEnvironmentSetUp($app);\n\n        $app['router']->get('/login', function () {\n            return 'login';\n        })->name(config('multitenancy.redirect_route'));\n    }\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->tenantMiddleware = new TenantMiddleware(app('auth'), app('multitenancy'));\n    }\n\n    protected function buildRequest($domain)\n    {\n        app('request')->headers->set('HOST', $domain . '.example.com');\n\n        return $this->tenantMiddleware->handle(app('request'), function () {\n            return (new Response())->setContent('<html></html>');\n        });\n    }\n\n    /** @test */\n    public function it_throws_error_if_domain_not_found()\n    {\n        $this->actingAs($this->testUser);\n\n        try {\n            $this->buildRequest('testdomain');\n            $this->fail('Expected exception not thrown');\n        } catch (TenantDoesNotExist $e) { //Not catching a generic Exception or the fail function is also catched\n            $this->assertEquals('There is no tenant at domain `testdomain`.', $e->getMessage());\n        }\n    }\n\n    /** @test **/\n    public function it_throws_error_if_user_is_not_part_of_tenant()\n    {\n        $this->actingAs($this->testUser);\n\n        try {\n            $this->buildRequest($this->testTenant->domain);\n            $this->fail('Expected exception not thrown');\n        } catch (UnauthorizedException $e) { //Not catching a generic Exception or the fail function is also catched\n            $this->assertEquals(403, $e->getStatusCode());\n            $this->assertEquals(\"The authenticated user does not have access to domain `{$this->testTenant->domain}`.\", $e->getMessage());\n        }\n    }\n\n    /** @test **/\n    public function it_throws_error_if_user_is_not_logged_in()\n    {\n        try {\n            $this->buildRequest($this->testTenant->domain);\n            $this->fail('Expected exception not thrown');\n        } catch (AuthenticationException $e) { //Not catching a generic Exception or the fail function is also catched\n            $this->assertEquals('Unauthenticated.', $e->getMessage());\n        }\n    }\n\n    /** @test **/\n    public function it_allows_users_who_are_associated_with_a_valid_domain()\n    {\n        $this->actingAs($this->testUser);\n\n        $this->testTenant->users()->sync($this->testUser);\n\n        $this->assertEquals(\n            $this->buildRequest($this->testTenant->domain)->getStatusCode(),\n            200\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Feature/MultitenancyTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse Illuminate\\Http\\Response;\nuse RomegaDigital\\Multitenancy\\Exceptions\\TenantDoesNotExist;\nuse RomegaDigital\\Multitenancy\\Middleware\\TenantMiddleware;\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\n\nclass MultitenancyTest extends TestCase\n{\n    protected $tenantMiddleware;\n\n    /**\n     * Define environment setup.\n     *\n     * @param  Illuminate\\Foundation\\Application  $app\n     *\n     * @return void\n     */\n    protected function getEnvironmentSetUp($app)\n    {\n        parent::getEnvironmentSetUp($app);\n\n        $app['router']->get('/login', function () {\n            return 'login';\n        })->name(config('multitenancy.redirect_route'));\n    }\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        $this->tenantMiddleware = new TenantMiddleware(app('auth'), app('multitenancy'));\n    }\n\n    protected function buildRequest($domain)\n    {\n        app('request')->headers->set('HOST', $domain.'.example.com');\n\n        return $this->tenantMiddleware->handle(app('request'), function () {\n            return (new Response())->setContent('<html></html>');\n        });\n    }\n\n    /** @test */\n    public function it_returns_the_current_tenant_when_set_by_middleware()\n    {\n        $this->actingAs($this->testUser);\n\n        $this->testTenant->users()->sync($this->testUser);\n\n        $this->buildRequest($this->testTenant->domain);\n\n        $this->assertEquals($this->testTenant->domain, app('multitenancy')->currentTenant()->domain);\n    }\n\n    /** @test */\n    public function it_throws_exception_when_tenant_not_set()\n    {\n        $this->actingAs($this->testUser);\n\n        $this->testTenant->users()->sync($this->testUser);\n\n        try {\n            $this->buildRequest('testdomain');\n            app('multitenancy')->currentTenant();\n            $this->fail('Expected exception not thrown');\n        } catch (TenantDoesNotExist $e) {\n            $this->assertEquals('There is no tenant at domain `testdomain`.', $e->getMessage());\n        }\n    }\n\n    /** @test */\n    public function it_throws_exception_when_tenant_not_set_never_touched_middleware()\n    {\n        $this->actingAs($this->testUser);\n\n        $this->testTenant->users()->sync($this->testUser);\n\n        try {\n            app('multitenancy')->currentTenant();\n            $this->fail('Expected exception not thrown');\n        } catch (TenantDoesNotExist $e) {\n            $this->assertEquals('There is no tenant at domain ``.', $e->getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Feature/TenantTest.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Feature;\n\nuse RomegaDigital\\Multitenancy\\Exceptions\\TenantDoesNotExist;\nuse RomegaDigital\\Multitenancy\\Models\\Tenant;\nuse RomegaDigital\\Multitenancy\\Tests\\TestCase;\n\nclass TenantTest extends TestCase\n{\n    /** @test */\n    public function it_throws_an_exception_if_domain_not_found()\n    {\n        $this->expectException(TenantDoesNotExist::class);\n        app(Tenant::class)->findByDomain('nonexistentdomain');\n    }\n\n    /** @test */\n    public function it_is_retrievable_by_domain()\n    {\n        $permission_by_domain = app(Tenant::class)->findByDomain($this->testTenant->domain);\n        $this->assertEquals($this->testTenant->id, $permission_by_domain->id);\n    }\n}\n"
  },
  {
    "path": "tests/Fixtures/Controllers/ProductController.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Controllers;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse PHPUnit\\Framework\\Assert;\nuse Illuminate\\Contracts\\Auth\\Access\\Gate;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Product;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse RomegaDigital\\Multitenancy\\Middleware\\TenantMiddleware;\n\nclass ProductController extends \\Illuminate\\Routing\\Controller\n{\n    use AuthorizesRequests;\n\n    public function __construct()\n    {\n        $this->middleware([\n            function ($request, Closure $next) {\n                $route = app('router')->getCurrentRoute();\n                Assert::assertSame(ProductController::class, get_class($route->getController()));\n\n                return $next($request);\n            },\n            TenantMiddleware::class,\n        ]);\n    }\n\n    public function index()\n    {\n        return Product::all();\n    }\n\n    public function store(Request $request)\n    {\n        return Product::create([\n            'name' => $request->name,\n        ]);\n    }\n\n    public function show(Product $product)\n    {\n        app(Gate::class)->authorize('view', $product);\n\n        return $product;\n    }\n}\n"
  },
  {
    "path": "tests/Fixtures/Controllers/UserController.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Controllers;\n\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse PHPUnit\\Framework\\Assert;\nuse RomegaDigital\\Multitenancy\\Models\\Tenant;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\User;\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse RomegaDigital\\Multitenancy\\Middleware\\TenantMiddleware;\n\nclass UserController extends \\Illuminate\\Routing\\Controller\n{\n    use AuthorizesRequests;\n\n    public function __construct()\n    {\n        $this->middleware([\n            function ($request, Closure $next) {\n                $route = app('router')->getCurrentRoute();\n                Assert::assertSame(UserController::class, get_class($route->getController()));\n\n                return $next($request);\n            },\n            TenantMiddleware::class,\n        ]);\n    }\n\n    public function store(Request $request)\n    {\n        if (! $request->has('tenant')) {\n            return User::create([\n                'email' => $request->email,\n                'name' => $request->name,\n                'password' => $request->password,\n            ]);\n        }\n\n        $user = new User([\n            'email' => $request->email,\n            'name' => $request->name,\n            'password' => $request->password,\n        ]);\n\n        resolve(Tenant::class)->find($request->tenant)->first()->users()->save($user);\n\n        return $user;\n    }\n}\n"
  },
  {
    "path": "tests/Fixtures/Policies/ProductPolicy.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Policies;\n\nclass ProductPolicy\n{\n    public function view()\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "tests/Fixtures/Product.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures;\n\nuse Illuminate\\Database\\Eloquent\\Model;\nuse RomegaDigital\\Multitenancy\\Traits\\BelongsToTenant;\n\nclass Product extends Model\n{\n    use BelongsToTenant;\n\n    public $timestamps = false;\n    protected $fillable = ['name', 'tenant_id'];\n}\n"
  },
  {
    "path": "tests/Fixtures/User.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests\\Fixtures;\n\nuse Illuminate\\Auth\\Authenticatable;\nuse Spatie\\Permission\\Traits\\HasRoles;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse RomegaDigital\\Multitenancy\\Traits\\HasTenants;\nuse Illuminate\\Foundation\\Auth\\Access\\Authorizable;\nuse Illuminate\\Contracts\\Auth\\Authenticatable as AuthenticatableContract;\nuse Illuminate\\Contracts\\Auth\\Access\\Authorizable as AuthorizableContract;\n\nclass User extends Model implements AuthorizableContract, AuthenticatableContract\n{\n    use HasRoles, HasTenants, Authorizable, Authenticatable;\n\n    /**\n     * The attributes that are mass assignable.\n     *\n     * @var array\n     */\n    protected $fillable = ['email','name','password'];\n    public $timestamps = false;\n    protected $table = 'users';\n    public $guard_name = 'web';\n}\n"
  },
  {
    "path": "tests/TestCase.php",
    "content": "<?php\n\nnamespace RomegaDigital\\Multitenancy\\Tests;\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse RomegaDigital\\Multitenancy\\Contracts\\Tenant;\nuse Spatie\\Permission\\PermissionServiceProvider;\nuse RomegaDigital\\Multitenancy\\MultitenancyFacade;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\User;\nuse Orchestra\\Testbench\\TestCase as OrchestraTestCase;\nuse RomegaDigital\\Multitenancy\\Tests\\Fixtures\\Product;\nuse RomegaDigital\\Multitenancy\\MultitenancyServiceProvider;\n\nclass TestCase extends OrchestraTestCase\n{\n    protected $testUser;\n    protected $testTenant;\n    protected $testAdminTenant;\n    protected $testProduct;\n\n    public $setupTestDatabase = true;\n\n    /**\n     * Set up the environment.\n     *\n     * @param \\Illuminate\\Foundation\\Application $app\n     */\n    protected function getEnvironmentSetUp($app)\n    {\n        $app['config']->set('multitenancy.user_model', User::class);\n        $app['config']->set('auth.providers.users.model', config('multitenancy.user_model'));\n        $app['config']->set('auth.guards.web.provider', 'users');\n    }\n\n    /**\n     * Load package service provider.\n     *\n     * @param \\Illuminate\\Foundation\\Application $app\n     *\n     * @return array\n     */\n    protected function getPackageProviders($app)\n    {\n        return [\n            MultitenancyServiceProvider::class,\n            PermissionServiceProvider::class,\n        ];\n    }\n\n    /**\n     * Load package alias.\n     *\n     * @param \\Illuminate\\Foundation\\Application $app\n     *\n     * @return array\n     */\n    protected function getPackageAliases($app)\n    {\n        return [\n            'Multitenancy' => MultitenancyFacade::class,\n        ];\n    }\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        if ($this->setupTestDatabase) {\n            $this->setUpDatabase($this->app);\n\n            $this->testUser = User::first();\n            $this->testTenant = app(Tenant::class)->find(1);\n            $this->testAdminTenant = app(Tenant::class)->find(2);\n            $this->testProduct = Product::first();\n        }\n    }\n\n    /**\n     * Define database migrations.\n     *\n     * @return void\n     */\n    protected function defineDatabaseMigrations()\n    {\n        $this->loadLaravelMigrations();\n    }\n\n    /**\n     * Set up the database.\n     *\n     * @param \\Illuminate\\Foundation\\Application $app\n     */\n    protected function setUpDatabase($app)\n    {\n        $this->loadMigrationsFrom(realpath(__DIR__ . '/../migrations'));\n        $this->artisan('migrate')->run();\n\n        $app[Tenant::class]->create([\n            'name' => 'Tenant Name',\n            'domain' => 'masterdomain',\n        ]);\n        $app[Tenant::class]->create([\n            'name' => 'Admin',\n            'domain' => 'admin',\n        ]);\n\n        User::create([\n            'name' => \"Test User\",\n            'email' => 'test@user.com',\n            'password' => 'testPassword',\n        ]);\n\n        Schema::create('products', function (Blueprint $table) {\n            $table->increments('id');\n            $table->string('name');\n            $table->unsignedInteger('tenant_id');\n            $table->foreign('tenant_id')\n                ->references('id')\n                ->on('tenants')\n                ->onDelete('cascade');\n            $table->softDeletes();\n        });\n        Product::create([\n            'name' => 'Product 1',\n            'tenant_id' => '1',\n        ]);\n    }\n}\n"
  }
]