From d56d7b1c8805833eb201afade2c09fa7d07dda69 Mon Sep 17 00:00:00 2001 From: Colin Date: Sat, 1 Sep 2018 10:57:52 +0100 Subject: [PATCH] Update for Hyn 5.2 and Laravel 5.6 --- .env | 11 +- .env.example | 6 + app/Console/Commands/CreateTenant.php | 20 +-- app/Console/Commands/DeleteTenant.php | 14 +- .../Controllers/Auth/RegisterController.php | 3 +- app/Http/Kernel.php | 6 +- .../Middleware/CheckForMaintenanceMode.php | 17 ++ app/Http/Middleware/TrustProxies.php | 4 +- app/Notifications/MailResetPasswordToken.php | 77 ++++++++ app/Tenant.php | 62 +++---- app/User.php | 9 + config/broadcasting.php | 3 +- config/filesystems.php | 3 +- config/hashing.php | 52 ++++++ config/logging.php | 81 +++++++++ config/permission.php | 20 +++ config/queue.php | 19 +- config/services.php | 2 +- config/tenancy.php | 3 - database/factories/UserFactory.php | 3 +- .../2017_01_01_000003_tenancy_websites.php | 39 +++++ .../2017_01_01_000005_tenancy_hostnames.php | 45 +++++ ..._000001_tenancy_websites_needs_db_host.php | 38 ++++ database/seeds/DatabaseSeeder.php | 2 +- phpunit.xml | 5 +- resources/assets/js/app.js | 11 +- resources/assets/js/bootstrap.js | 20 ++- .../assets/js/components/ExampleComponent.vue | 12 +- resources/assets/sass/_variables.scss | 46 ++--- resources/assets/sass/app.scss | 164 +----------------- resources/lang/en/validation.php | 27 ++- resources/views/auth/login.blade.php | 97 +++++------ .../views/auth/passwords/email.blade.php | 30 ++-- .../views/auth/passwords/reset.blade.php | 47 +++-- resources/views/auth/register.blade.php | 48 ++--- resources/views/home.blade.php | 12 +- resources/views/layouts/app.blade.php | 75 +++++--- resources/views/welcome.blade.php | 7 +- tests/Feature/ProfileTest.php | 6 +- tests/Feature/TenantCreateCommandTest.php | 30 +++- tests/Feature/TenantDeleteCommandTest.php | 13 +- tests/TenantAwareTestCase.php | 6 +- 42 files changed, 752 insertions(+), 443 deletions(-) create mode 100644 app/Http/Middleware/CheckForMaintenanceMode.php create mode 100644 app/Notifications/MailResetPasswordToken.php create mode 100644 config/hashing.php create mode 100644 config/logging.php create mode 100644 database/migrations/2017_01_01_000003_tenancy_websites.php create mode 100644 database/migrations/2017_01_01_000005_tenancy_hostnames.php create mode 100644 database/migrations/2018_04_06_000001_tenancy_websites_needs_db_host.php diff --git a/.env b/.env index e3a8fa3..a605c28 100644 --- a/.env +++ b/.env @@ -33,7 +33,14 @@ MAIL_ENCRYPTION=null PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= +PUSHER_APP_CLUSTER=mt1 + +MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" LIMIT_UUID_LENGTH_32=true -AUTO_DELETE_TENANT_DIRECTORY=true -AUTO_DELETE_TENANT_DATABASE=true +TENANCY_DEFAULT_HOSTNAME=townhouse.test +TENANCY_EARLY_IDENTIFICATION=true +TENANCY_DATABASE_AUTO_DELETE=true +TENANCY_DATABASE_AUTO_DELETE_USER=true +ABORT_WITHOUT_IDENTIFIED_HOSTNAME=false diff --git a/.env.example b/.env.example index 91ba58f..c3604db 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,8 @@ APP_DEBUG=true APP_LOG_LEVEL=debug APP_URL=http://localhost +LOG_CHANNEL=stack + DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 @@ -32,3 +34,7 @@ MAIL_ENCRYPTION=null PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= +PUSHER_APP_CLUSTER=mt1 + +MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" diff --git a/app/Console/Commands/CreateTenant.php b/app/Console/Commands/CreateTenant.php index 7f4bab0..3043531 100644 --- a/app/Console/Commands/CreateTenant.php +++ b/app/Console/Commands/CreateTenant.php @@ -4,36 +4,30 @@ use App\Notifications\TenantCreated; use App\Tenant; -use Hyn\Tenancy\Models\Customer; use Illuminate\Console\Command; class CreateTenant extends Command { - protected $signature = 'tenant:create {name} {email}'; + protected $signature = 'tenant:create {name} {password} {email}'; - protected $description = 'Creates a tenant with the provided name and email address e.g. php artisan tenant:create boise boise@example.com'; + protected $description = 'Creates a tenant with the provided name and email address e.g. php artisan tenant:create boise test boise@example.com'; public function handle() { $name = $this->argument('name'); $email = $this->argument('email'); + $password = $this->argument('password'); - if ($this->tenantExists($name, $email)) { - $this->error("A tenant with name '{$name}' and/or '{$email}' already exists."); - + if (Tenant::tenantExists($name)) { + $this->error("A tenant with name '{$name}' already exists."); return; } - $tenant = Tenant::createFrom($name, $email); + $tenant = Tenant::registerTenant($name, $email, $password); $this->info("Tenant '{$name}' is created and is now accessible at {$tenant->hostname->fqdn}"); // invite admin $tenant->admin->notify(new TenantCreated($tenant->hostname)); - $this->info("Admin {$email} has been invited!"); - } - - private function tenantExists($name, $email): bool - { - return Customer::where('name', $name)->orWhere('email', $email)->exists(); + $this->info("Admin {$email} can log in using password {$password}"); } } diff --git a/app/Console/Commands/DeleteTenant.php b/app/Console/Commands/DeleteTenant.php index 6d21382..ee66a97 100644 --- a/app/Console/Commands/DeleteTenant.php +++ b/app/Console/Commands/DeleteTenant.php @@ -13,19 +13,15 @@ class DeleteTenant extends Command public function handle() { // because this is a destructive command, we'll only allow to run this command - // if the environment is local or testing - if (!(app()->isLocal() || app()->runningUnitTests())) { - $this->error('This command is only avilable on the local environment.'); + // if you are on the local environment or testing + if (!app()->isLocal() && !app()->runningUnitTests()) { + $this->error('This command is only available on the local environment.'); return; } $name = $this->argument('name'); - if ($tenant = Tenant::retrieveBy($name)) { - $tenant->delete(); - $this->info("Tenant {$name} successfully deleted."); - } else { - $this->error("Couldn't find tenant {$name}"); - } + $result = Tenant::delete($name); + $this->info($result); } } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index f77265a..e749c07 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -4,6 +4,7 @@ use App\User; use App\Http\Controllers\Controller; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Foundation\Auth\RegistersUsers; @@ -65,7 +66,7 @@ protected function create(array $data) return User::create([ 'name' => $data['name'], 'email' => $data['email'], - 'password' => bcrypt($data['password']), + 'password' => Hash::make($data['password']), ]); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 1782366..7a6f311 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -14,7 +14,7 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, + \App\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, @@ -54,9 +54,11 @@ class Kernel extends HttpKernel 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'tenancy.enforce' => \App\Http\Middleware\EnforceTenancy::class + 'tenancy.enforce' => \App\Http\Middleware\EnforceTenancy::class, ]; } diff --git a/app/Http/Middleware/CheckForMaintenanceMode.php b/app/Http/Middleware/CheckForMaintenanceMode.php new file mode 100644 index 0000000..35b9824 --- /dev/null +++ b/app/Http/Middleware/CheckForMaintenanceMode.php @@ -0,0 +1,17 @@ +token = $token; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + // Get current Hostname + $hostname = app(\Hyn\Tenancy\Environment::class)->hostname(); + + // Get FQDN (Fully-Qualified Domain Name) by current hostname + $fqdn = $hostname->fqdn; + + return (new MailMessage) + ->subject('Reset Password Notification') + ->line('You are receiving this email because we received a password reset request for your account.') + ->action('Reset Password', $fqdn.route('password.reset', $this->token, false)) + ->line('If you did not request a password reset, no further action is required.'); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/app/Tenant.php b/app/Tenant.php index aec35f0..d2f6c38 100644 --- a/app/Tenant.php +++ b/app/Tenant.php @@ -3,63 +3,68 @@ namespace App; use Hyn\Tenancy\Environment; -use Hyn\Tenancy\Models\Customer; use Hyn\Tenancy\Models\Hostname; use Hyn\Tenancy\Models\Website; use Illuminate\Support\Facades\Hash; -use Hyn\Tenancy\Contracts\Repositories\CustomerRepository; use Hyn\Tenancy\Contracts\Repositories\HostnameRepository; use Hyn\Tenancy\Contracts\Repositories\WebsiteRepository; /** - * @property Customer customer * @property Website website * @property Hostname hostname * @property User admin */ class Tenant { - public function __construct(Customer $customer, Website $website = null, Hostname $hostname = null, User $admin = null) + public function __construct(Website $website = null, Hostname $hostname = null, User $admin = null) { - $this->customer = $customer; - $this->website = $website ?? $customer->websites->first(); - $this->hostname = $hostname ?? $customer->hostnames->first(); + $this->website = $website; + $this->hostname = $hostname; $this->admin = $admin; } - public function delete() + public static function delete($name) { - app(HostnameRepository::class)->delete($this->hostname, true); - app(WebsiteRepository::class)->delete($this->website, true); - app(CustomerRepository::class)->delete($this->customer, true); + $baseUrl = env('APP_URL_BASE'); + $name = "{$name}.{$baseUrl}"; + if ($tenant = Hostname::where('fqdn', $name)->firstOrFail()) { + app(HostnameRepository::class)->delete($tenant, true); + app(WebsiteRepository::class)->delete($tenant->website, true); + return "Tenant {$name} successfully deleted."; + } } - public static function createFrom($name, $email, $password = null): Tenant + public static function deleteByFqdn($fqdn) { - // create a customer - $customer = new Customer; - $customer->name = $name; - $customer->email = $email; + if ($tenant = Hostname::where('fqdn', $fqdn)->firstOrFail()) { + app(HostnameRepository::class)->delete($tenant, true); + app(WebsiteRepository::class)->delete($tenant->website, true); + return "Tenant {$fqdn} successfully deleted."; + } + } - app(CustomerRepository::class)->create($customer); + public static function registerTenant($name, $email, $password): Tenant + { + // Convert all to lowercase + $name = strtolower($name); + $email = strtolower($email); - // associate the customer with a website $website = new Website; - $website->customer()->associate($customer); app(WebsiteRepository::class)->create($website); // associate the website with a hostname $hostname = new Hostname; - $baseUrl = config('app.url_base'); + $baseUrl = env('APP_URL_BASE'); $hostname->fqdn = "{$name}.{$baseUrl}"; - $hostname->customer()->associate($customer); app(HostnameRepository::class)->attach($hostname, $website); + // make hostname current - app(Environment::class)->hostname($hostname); + app(Environment::class)->tenant($hostname->website); - $admin = static::makeAdmin($name, $email, $password ?: str_random()); + // Make the registered user the default Admin of the site. + $admin = static::makeAdmin($name, $email, $password); - return new Tenant($customer, $website, $hostname, $admin); + return new Tenant($website, $hostname, $admin); } private static function makeAdmin($name, $email, $password): User @@ -71,12 +76,9 @@ private static function makeAdmin($name, $email, $password): User return $admin; } - public static function retrieveBy($name): ?Tenant + public static function tenantExists($name) { - if ($customer = Customer::where('name', $name)->with(['websites', 'hostnames'])->first()) { - return new Tenant($customer); - } - - return null; + $name = $name . '.' . env('APP_URL_BASE'); + return Hostname::where('fqdn', $name)->exists(); } } diff --git a/app/User.php b/app/User.php index 08844ba..71e8703 100644 --- a/app/User.php +++ b/app/User.php @@ -6,6 +6,7 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Spatie\Permission\Traits\HasRoles; +use App\Notifications\MailResetPasswordToken; class User extends Authenticatable { @@ -28,4 +29,12 @@ class User extends Authenticatable protected $hidden = [ 'password', 'remember_token', ]; + + /** + * Send a password reset email to the user + */ + public function sendPasswordResetNotification($token) + { + $this->notify(new MailResetPasswordToken($token)); + } } diff --git a/config/broadcasting.php b/config/broadcasting.php index 5eecd2b..3ca45ea 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -36,7 +36,8 @@ 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ - // + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'encrypted' => true, ], ], diff --git a/config/filesystems.php b/config/filesystems.php index 9568e02..77fa5de 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -37,7 +37,7 @@ | may even configure multiple disks of the same driver. Defaults have | been setup for each driver as an example of the required options. | - | Supported Drivers: "local", "ftp", "s3", "rackspace" + | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace" | */ @@ -61,6 +61,7 @@ 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), ], ], diff --git a/config/hashing.php b/config/hashing.php new file mode 100644 index 0000000..d3c8e2f --- /dev/null +++ b/config/hashing.php @@ -0,0 +1,52 @@ + 'bcrypt', + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 10), + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => 1024, + 'threads' => 2, + 'time' => 2, + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 0000000..400bc7f --- /dev/null +++ b/config/logging.php @@ -0,0 +1,81 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", + | "custom", "stack" + | + */ + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['single'], + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => 'debug', + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => 'debug', + 'days' => 7, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => 'Laravel Log', + 'emoji' => ':boom:', + 'level' => 'critical', + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'handler' => StreamHandler::class, + 'with' => [ + 'stream' => 'php://stderr', + ], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => 'debug', + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => 'debug', + ], + ], + +]; diff --git a/config/permission.php b/config/permission.php index e8aad89..ab2c0d4 100644 --- a/config/permission.php +++ b/config/permission.php @@ -71,10 +71,30 @@ 'role_has_permissions' => 'role_has_permissions', ], + 'column_names' => [ + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + 'model_morph_key' => 'model_id', + ], + /* * By default all permissions will be cached for 24 hours unless a permission or * role is updated. Then the cache will be flushed immediately. */ 'cache_expiration_time' => 60 * 24, + + /* + * When set to true, the required permission/role names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, ]; diff --git a/config/queue.php b/config/queue.php index 4d83ebd..391304f 100644 --- a/config/queue.php +++ b/config/queue.php @@ -4,14 +4,12 @@ /* |-------------------------------------------------------------------------- - | Default Queue Driver + | Default Queue Connection Name |-------------------------------------------------------------------------- | | Laravel's queue API supports an assortment of back-ends via a single | API, giving you convenient access to each back-end using the same - | syntax for each one. Here you may set the default queue driver. - | - | Supported: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | syntax for every one. Here you may define a default connection. | */ @@ -26,6 +24,8 @@ | is used by your application. A default configuration has been added | for each back-end shipped with Laravel. You are free to add more. | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | */ 'connections' => [ @@ -50,11 +50,11 @@ 'sqs' => [ 'driver' => 'sqs', - 'key' => 'your-public-key', - 'secret' => 'your-secret-key', - 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', - 'queue' => 'your-queue-name', - 'region' => 'us-east-1', + 'key' => env('SQS_KEY', 'your-public-key'), + 'secret' => env('SQS_SECRET', 'your-secret-key'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'your-queue-name'), + 'region' => env('SQS_REGION', 'us-east-1'), ], 'redis' => [ @@ -62,6 +62,7 @@ 'connection' => 'default', 'queue' => 'default', 'retry_after' => 90, + 'block_for' => null, ], ], diff --git a/config/services.php b/config/services.php index 4460f0e..aa1f7f8 100644 --- a/config/services.php +++ b/config/services.php @@ -22,7 +22,7 @@ 'ses' => [ 'key' => env('SES_KEY'), 'secret' => env('SES_SECRET'), - 'region' => 'us-east-1', + 'region' => env('SES_REGION', 'us-east-1'), ], 'sparkpost' => [ diff --git a/config/tenancy.php b/config/tenancy.php index 73af37f..ffa97a5 100644 --- a/config/tenancy.php +++ b/config/tenancy.php @@ -24,9 +24,6 @@ * UsesSystemConnection. */ - // Must implement \Hyn\Tenancy\Contracts\Customer - 'customer' => \Hyn\Tenancy\Models\Customer::class, - // Must implement \Hyn\Tenancy\Contracts\Hostname 'hostname' => \Hyn\Tenancy\Models\Hostname::class, diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 061d75a..6476064 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -1,6 +1,7 @@ $faker->name, 'email' => $faker->unique()->safeEmail, - 'password' => $password ?: $password = bcrypt('secret'), + 'password' => $password ?: $password = Hash::make('secret'), 'remember_token' => str_random(10), ]; }); diff --git a/database/migrations/2017_01_01_000003_tenancy_websites.php b/database/migrations/2017_01_01_000003_tenancy_websites.php new file mode 100644 index 0000000..76b406c --- /dev/null +++ b/database/migrations/2017_01_01_000003_tenancy_websites.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @see https://laravel-tenancy.com + * @see https://github.com/hyn/multi-tenant + */ + +use Hyn\Tenancy\Abstracts\AbstractMigration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class TenancyWebsites extends AbstractMigration +{ + protected $system = true; + + public function up() + { + Schema::create('websites', function (Blueprint $table) { + $table->bigIncrements('id'); + + $table->string('uuid'); + + $table->timestamps(); + $table->softDeletes(); + }); + } + + public function down() + { + Schema::dropIfExists('websites'); + } +} diff --git a/database/migrations/2017_01_01_000005_tenancy_hostnames.php b/database/migrations/2017_01_01_000005_tenancy_hostnames.php new file mode 100644 index 0000000..af10558 --- /dev/null +++ b/database/migrations/2017_01_01_000005_tenancy_hostnames.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @see https://laravel-tenancy.com + * @see https://github.com/hyn/multi-tenant + */ + +use Hyn\Tenancy\Abstracts\AbstractMigration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class TenancyHostnames extends AbstractMigration +{ + protected $system = true; + + public function up() + { + Schema::create('hostnames', function (Blueprint $table) { + $table->bigIncrements('id'); + + $table->string('fqdn')->unique(); + $table->string('redirect_to')->nullable(); + $table->boolean('force_https')->default(false); + $table->timestamp('under_maintenance_since')->nullable(); + $table->bigInteger('website_id')->unsigned()->nullable(); + + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('website_id')->references('id')->on('websites')->onDelete('set null'); + }); + } + + public function down() + { + Schema::dropIfExists('hostnames'); + } +} diff --git a/database/migrations/2018_04_06_000001_tenancy_websites_needs_db_host.php b/database/migrations/2018_04_06_000001_tenancy_websites_needs_db_host.php new file mode 100644 index 0000000..6ea85fb --- /dev/null +++ b/database/migrations/2018_04_06_000001_tenancy_websites_needs_db_host.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @see https://laravel-tenancy.com + * @see https://github.com/hyn/multi-tenant + */ + +use Hyn\Tenancy\Abstracts\AbstractMigration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class TenancyWebsitesNeedsDbHost extends AbstractMigration +{ + protected $system = true; + + public function up() + { + Schema::table('websites', function (Blueprint $table) { + $table->string('managed_by_database_connection') + ->nullable() + ->comment('References the database connection key in your database.php'); + }); + } + + public function down() + { + Schema::table('websites', function (Blueprint $table) { + $table->dropColumn('managed_by_database_connection'); + }); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index e119db6..91cb6d1 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -5,7 +5,7 @@ class DatabaseSeeder extends Seeder { /** - * Run the database seeds. + * Seed the application's database. * * @return void */ diff --git a/phpunit.xml b/phpunit.xml index 9d8c4fc..7eac1dd 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -24,10 +24,13 @@ + - + + + diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index 1f27089..98eca79 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -1,12 +1,13 @@ + /** * First we will load all of this project's JavaScript dependencies which * includes Vue and other libraries. It is a great starting point when * building robust, powerful web applications using Vue and Laravel. */ -require('./bootstrap') +require('./bootstrap'); -window.Vue = require('vue') +window.Vue = require('vue'); /** * Next, we will create a fresh Vue application instance and attach it to @@ -14,8 +15,8 @@ window.Vue = require('vue') * or customize the JavaScript scaffolding to fit your unique needs. */ -Vue.component('example-component', require('./components/ExampleComponent.vue')) +Vue.component('example-component', require('./components/ExampleComponent.vue')); const app = new Vue({ - el: '#app' -}) + el: '#app' +}); diff --git a/resources/assets/js/bootstrap.js b/resources/assets/js/bootstrap.js index aba1734..40be0c1 100644 --- a/resources/assets/js/bootstrap.js +++ b/resources/assets/js/bootstrap.js @@ -1,4 +1,18 @@ -window._ = require('lodash') + +window._ = require('lodash'); +window.Popper = require('popper.js').default; + +/** + * We'll load jQuery and the Bootstrap jQuery plugin which provides support + * for JavaScript based Bootstrap features such as modals and tabs. This + * code may be modified to fit the specific needs of your application. + */ + +try { + window.$ = window.jQuery = require('jquery'); + + require('bootstrap'); +} catch (e) {} /** * We'll load the axios HTTP library which allows us to easily issue requests @@ -6,9 +20,9 @@ window._ = require('lodash') * CSRF token as a header based on the value of the "XSRF" token cookie. */ -window.axios = require('axios') +window.axios = require('axios'); -window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest' +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; /** * Next we will register the CSRF Token as a common header with Axios so that diff --git a/resources/assets/js/components/ExampleComponent.vue b/resources/assets/js/components/ExampleComponent.vue index 601e61c..2805329 100644 --- a/resources/assets/js/components/ExampleComponent.vue +++ b/resources/assets/js/components/ExampleComponent.vue @@ -1,12 +1,12 @@