add: full multi-tenancy control

This commit is contained in:
Cauê Faleiros
2026-02-02 15:31:15 -03:00
commit c6ec92802b
1711 changed files with 258106 additions and 0 deletions

View File

@@ -0,0 +1,495 @@
<?php
namespace Webkul\Installer\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\File;
use Webkul\Core\Providers\CoreServiceProvider;
use Webkul\Installer\Database\Seeders\DatabaseSeeder as KrayinDatabaseSeeder;
use Webkul\Installer\Events\ComposerEvents;
use function Laravel\Prompts\password;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;
class Installer extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'krayin-crm:install
{ --skip-env-check : Skip env check. }
{ --skip-admin-creation : Skip admin creation. }';
/**
* The console command description.
*
* @var string
*/
protected $description = 'krayin installer.';
/**
* Locales list.
*
* @var array
*/
protected $locales = [
'ar' => 'Arabic',
'en' => 'English',
'tr' => 'Turkish',
'es' => 'Spanish',
'fa' => 'Persian',
'pt_BR' => 'Portuguese',
];
/**
* Currencies list.
*
* @var array
*/
protected $currencies = [
'AED' => 'United Arab Emirates Dirham',
'ARS' => 'Argentine Peso',
'AUD' => 'Australian Dollar',
'BDT' => 'Bangladeshi Taka',
'BRL' => 'Brazilian Real',
'CAD' => 'Canadian Dollar',
'CHF' => 'Swiss Franc',
'CLP' => 'Chilean Peso',
'CNY' => 'Chinese Yuan',
'COP' => 'Colombian Peso',
'CZK' => 'Czech Koruna',
'DKK' => 'Danish Krone',
'DZD' => 'Algerian Dinar',
'EGP' => 'Egyptian Pound',
'EUR' => 'Euro',
'FJD' => 'Fijian Dollar',
'GBP' => 'British Pound Sterling',
'HKD' => 'Hong Kong Dollar',
'HUF' => 'Hungarian Forint',
'IDR' => 'Indonesian Rupiah',
'ILS' => 'Israeli New Shekel',
'INR' => 'Indian Rupee',
'JOD' => 'Jordanian Dinar',
'JPY' => 'Japanese Yen',
'KRW' => 'South Korean Won',
'KWD' => 'Kuwaiti Dinar',
'KZT' => 'Kazakhstani Tenge',
'LBP' => 'Lebanese Pound',
'LKR' => 'Sri Lankan Rupee',
'LYD' => 'Libyan Dinar',
'MAD' => 'Moroccan Dirham',
'MUR' => 'Mauritian Rupee',
'MXN' => 'Mexican Peso',
'MYR' => 'Malaysian Ringgit',
'NGN' => 'Nigerian Naira',
'NOK' => 'Norwegian Krone',
'NPR' => 'Nepalese Rupee',
'NZD' => 'New Zealand Dollar',
'OMR' => 'Omani Rial',
'PAB' => 'Panamanian Balboa',
'PEN' => 'Peruvian Nuevo Sol',
'PHP' => 'Philippine Peso',
'PKR' => 'Pakistani Rupee',
'PLN' => 'Polish Zloty',
'PYG' => 'Paraguayan Guarani',
'QAR' => 'Qatari Rial',
'RON' => 'Romanian Leu',
'RUB' => 'Russian Ruble',
'SAR' => 'Saudi Riyal',
'SEK' => 'Swedish Krona',
'SGD' => 'Singapore Dollar',
'THB' => 'Thai Baht',
'TND' => 'Tunisian Dinar',
'TRY' => 'Turkish Lira',
'TWD' => 'New Taiwan Dollar',
'UAH' => 'Ukrainian Hryvnia',
'USD' => 'United States Dollar',
'UZS' => 'Uzbekistani Som',
'VEF' => 'Venezuelan Bolívar',
'VND' => 'Vietnamese Dong',
'XAF' => 'CFA Franc BEAC',
'XOF' => 'CFA Franc BCEAO',
'ZAR' => 'South African Rand',
'ZMW' => 'Zambian Kwacha',
];
/**
* Install and configure krayin.
*/
public function handle()
{
$applicationDetails = ! $this->option('skip-env-check')
? $this->checkForEnvFile()
: [];
$this->loadEnvConfigAtRuntime();
$this->warn('Step: Generating key...');
$this->call('key:generate');
$this->warn('Step: Migrating all tables...');
$this->call('migrate:fresh');
$this->warn('Step: Seeding basic data for Krayin kickstart...');
$this->info(app(KrayinDatabaseSeeder::class)->run([
'locale' => $applicationDetails['locale'] ?? 'en',
'currency' => $applicationDetails['currency'] ?? 'USD',
]));
$this->warn('Step: Publishing assets and configurations...');
$result = $this->call('vendor:publish', ['--provider' => CoreServiceProvider::class, '--force' => true]);
$this->info($result);
$this->warn('Step: Linking storage directory...');
$this->call('storage:link');
$this->warn('Step: Clearing cached bootstrap files...');
$this->call('optimize:clear');
if (! $this->option('skip-admin-creation')) {
$this->warn('Step: Create admin credentials...');
$this->createAdminCredentials();
}
ComposerEvents::postCreateProject();
}
/**
* Checking .env file and if not found then create .env file
*
* @return ?array
*/
protected function checkForEnvFile()
{
if (! file_exists(base_path('.env'))) {
$this->info('Creating the environment configuration file.');
File::copy('.env.example', '.env');
} else {
$this->info('Great! your environment configuration file already exists.');
}
return $this->createEnvFile();
}
/**
* Create a new .env file. Afterwards, request environment configuration details and set them
* in the .env file to facilitate the migration to our database.
*
* @return ?array
*/
protected function createEnvFile()
{
try {
$applicationDetails = $this->askForApplicationDetails();
$this->askForDatabaseDetails();
return $applicationDetails;
} catch (\Exception $e) {
$this->error('Error in creating .env file, please create it manually and then run `php artisan migrate` again.');
}
}
/**
* Ask for application details.
*
* @return void
*/
protected function askForApplicationDetails()
{
$this->updateEnvVariable(
'APP_NAME',
'Please enter the application name',
env('APP_NAME', 'Krayin CRM')
);
$this->updateEnvVariable(
'APP_URL',
'Please enter the application URL',
env('APP_URL', 'http://localhost:8000')
);
$this->envUpdate(
'APP_TIMEZONE',
date_default_timezone_get()
);
$this->info('Your Default Timezone is '.date_default_timezone_get());
$locale = $this->updateEnvChoice(
'APP_LOCALE',
'Please select the default application locale',
$this->locales
);
$currency = $this->updateEnvChoice(
'APP_CURRENCY',
'Please select the default currency',
$this->currencies
);
return [
'locale' => $locale,
'currency' => $currency,
];
}
/**
* Add the database credentials to the .env file.
*/
protected function askForDatabaseDetails()
{
$databaseDetails = [
'DB_CONNECTION' => select(
'Please select the database connection',
['mysql', 'pgsql', 'sqlsrv']
),
'DB_HOST' => text(
label: 'Please enter the database host',
default: env('DB_HOST', '127.0.0.1'),
required: true
),
'DB_PORT' => text(
label: 'Please enter the database port',
default: env('DB_PORT', '3306'),
required: true
),
'DB_DATABASE' => text(
label: 'Please enter the database name',
default: env('DB_DATABASE', ''),
required: true
),
'DB_PREFIX' => text(
label: 'Please enter the database prefix',
default: env('DB_PREFIX', ''),
hint: 'or press enter to continue',
validate: function ($value) {
$input = strlen($value);
if ($input
&& ($input < 1
|| $input > 6)
) {
return 'The database prefix must be between 1 and 6 characters.';
}
if (preg_match('/[^a-zA-Z0-9_]/', $value)) {
return 'The database prefix may only contain letters, numbers, and underscores.';
}
return null;
}
),
'DB_USERNAME' => text(
label: 'Please enter your database username',
default: env('DB_USERNAME', ''),
required: true
),
'DB_PASSWORD' => password(
label: 'Please enter your database password',
required: true
),
];
if (
! $databaseDetails['DB_DATABASE']
|| ! $databaseDetails['DB_USERNAME']
|| ! $databaseDetails['DB_PASSWORD']
) {
return $this->error('Please enter the database credentials.');
}
foreach ($databaseDetails as $key => $value) {
if ($value) {
$this->envUpdate($key, $value);
}
}
}
/**
* Create a admin credentials.
*
* @return mixed
*/
protected function createAdminCredentials()
{
$adminName = text(
label: 'Enter the name of the admin user',
default: 'Example',
required: true
);
$adminEmail = text(
label: 'Enter the email address of the admin user',
default: 'admin@example.com',
validate: fn (string $value) => match (true) {
! filter_var($value, FILTER_VALIDATE_EMAIL) => 'The email address you entered is not valid please try again.',
default => null
}
);
$adminPassword = text(
label: 'Configure the password for the admin user',
default: 'admin123',
required: true
);
$password = password_hash($adminPassword, PASSWORD_BCRYPT, ['cost' => 10]);
try {
DB::table('users')->updateOrInsert(
['id' => 1],
[
'name' => $adminName,
'email' => $adminEmail,
'password' => $password,
'role_id' => 1,
'status' => 1,
]
);
$filePath = storage_path('installed');
File::put($filePath, 'Krayin is successfully installed');
$this->info('-----------------------------');
$this->info('Congratulations!');
$this->info('The installation has been finished and you can now use Krayin.');
$this->info('Go to '.env('APP_URL').'/admin/dashboard'.' and authenticate with:');
$this->info('Email: '.$adminEmail);
$this->info('Password: '.$adminPassword);
$this->info('Cheers!');
Event::dispatch('krayin.installed');
} catch (\Exception $e) {
return $this->error($e->getMessage());
}
}
/**
* Loaded Env variables for config files.
*/
protected function loadEnvConfigAtRuntime(): void
{
$this->warn('Loading configs...');
/**
* Setting application environment.
*/
app()['env'] = $this->getEnvAtRuntime('APP_ENV');
/**
* Setting application configuration.
*/
config([
'app.env' => $this->getEnvAtRuntime('APP_ENV'),
'app.name' => $this->getEnvAtRuntime('APP_NAME'),
'app.url' => $this->getEnvAtRuntime('APP_URL'),
'app.timezone' => $this->getEnvAtRuntime('APP_TIMEZONE'),
'app.locale' => $this->getEnvAtRuntime('APP_LOCALE'),
'app.currency' => $this->getEnvAtRuntime('APP_CURRENCY'),
]);
/**
* Setting database configurations.
*/
$databaseConnection = $this->getEnvAtRuntime('DB_CONNECTION');
config([
"database.connections.{$databaseConnection}.host" => $this->getEnvAtRuntime('DB_HOST'),
"database.connections.{$databaseConnection}.port" => $this->getEnvAtRuntime('DB_PORT'),
"database.connections.{$databaseConnection}.database" => $this->getEnvAtRuntime('DB_DATABASE'),
"database.connections.{$databaseConnection}.username" => $this->getEnvAtRuntime('DB_USERNAME'),
"database.connections.{$databaseConnection}.password" => $this->getEnvAtRuntime('DB_PASSWORD'),
"database.connections.{$databaseConnection}.prefix" => $this->getEnvAtRuntime('DB_PREFIX'),
]);
DB::purge($databaseConnection);
$this->info('Configuration loaded...');
}
/**
* Method for asking the details of .env files
*/
protected function updateEnvVariable(string $key, string $question, string $defaultValue): void
{
$input = text(
label: $question,
default: $defaultValue,
required: true
);
$this->envUpdate($key, $input ?: $defaultValue);
}
/**
* Method for asking choice based on the list of options.
*
* @return string
*/
protected function updateEnvChoice(string $key, string $question, array $choices)
{
$choice = select(
label: $question,
options: $choices,
default: env($key)
);
$this->envUpdate($key, $choice);
return $choice;
}
/**
* Update the .env values.
*/
protected function envUpdate(string $key, string $value): void
{
$data = file_get_contents(base_path('.env'));
// Check if $value contains spaces, and if so, add double quotes
if (preg_match('/\s/', $value)) {
$value = '"'.$value.'"';
}
$data = preg_replace("/$key=(.*)/", "$key=$value", $data);
file_put_contents(base_path('.env'), $data);
}
/**
* Check key in `.env` file because it will help to find values at runtime.
*/
protected static function getEnvAtRuntime(string $key): string|bool
{
if ($data = file(base_path('.env'))) {
foreach ($data as $line) {
$line = preg_replace('/\s+/', '', $line);
$rowValues = explode('=', $line);
if (strlen($line) !== 0) {
if (strpos($key, $rowValues[0]) !== false) {
return $rowValues[1];
}
}
}
}
return false;
}
}