From 77b8b595bafe595fd6a8a12e504dbc2e1cad1ab9 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 17 Jul 2023 18:48:03 +0800 Subject: [PATCH 1/2] wip --- composer.json | 3 +- src/Activate/CoreServiceProvider.php | 357 ++++++++++++++++++ src/Console/Commands/MigrateSandbox.php | 5 +- src/Exceptions/FleetbaseRequestException.php | 7 +- .../FleetbaseRequestValidationException.php | 20 + .../PendingResourceRegistration.php | 2 + src/Models/User.php | 4 +- src/Providers/CoreServiceProvider.php | 351 +---------------- src/Support/Utils.php | 16 + 9 files changed, 405 insertions(+), 360 deletions(-) create mode 100644 src/Activate/CoreServiceProvider.php create mode 100644 src/Exceptions/FleetbaseRequestValidationException.php diff --git a/composer.json b/composer.json index 5ffe5db..e894ca9 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,6 @@ "illuminate/routing": "^7.0|^8.0|^9.0", "illuminate/support": "^7.0|^8.0|^9.0", "jdorn/sql-formatter": "^1.2", - "laravel/cashier": "^13.15", "laravel/sanctum": "^2.15", "maatwebsite/excel": "^3.1", "phpoffice/phpspreadsheet": "^1.28", @@ -67,7 +66,7 @@ "extra": { "laravel": { "providers": [ - "Fleetbase\\Providers\\CoreServiceProvider", + "Fleetbase\\Activate\\CoreServiceProvider", "Fleetbase\\Providers\\EventServiceProvider", "Fleetbase\\Providers\\SocketClusterServiceProvider" ] diff --git a/src/Activate/CoreServiceProvider.php b/src/Activate/CoreServiceProvider.php new file mode 100644 index 0000000..082f8c3 --- /dev/null +++ b/src/Activate/CoreServiceProvider.php @@ -0,0 +1,357 @@ + \Fleetbase\Observers\UserObserver::class, + \Fleetbase\Models\ApiCredential::class => \Fleetbase\Observers\ApiCredentialObserver::class, + \Spatie\Activitylog\Models\Activity::class => \Fleetbase\Observers\ActivityObserver::class, + ]; + + /** + * The middleware groups registered with the service provider. + * + * @var array + */ + public $middleware = [ + 'fleetbase.protected' => [ + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + 'auth:sanctum', + \Fleetbase\Http\Middleware\SetupFleetbaseSession::class + ] + ]; + + /** + * The console commands registered with the service provider. + * + * @var array + */ + public $commands = [ + \Fleetbase\Console\Commands\CreateDatabase::class, + \Fleetbase\Console\Commands\SeedDatabase::class, + \Fleetbase\Console\Commands\MigrateSandbox::class, + \Fleetbase\Console\Commands\InitializeSandboxKeyColumn::class, + \Fleetbase\Console\Commands\SyncSandbox::class, + \Fleetbase\Console\Commands\BackupDatabase\MysqlS3Backup::class + ]; + + /** + * Bootstrap any package services. + * + * @return void + */ + public function boot() + { + JsonResource::withoutWrapping(); + + $this->registerCommands(); + $this->registerObservers(); + $this->registerExpansionsFrom(); + $this->registerMiddleware(); + $this->loadRoutesFrom(__DIR__ . '/../routes.php'); + $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); + $this->mergeConfigFrom(__DIR__ . '/../../config/database.connections.php', 'database.connections'); + $this->mergeConfigFrom(__DIR__ . '/../../config/database.redis.php', 'database.redis'); + $this->mergeConfigFrom(__DIR__ . '/../../config/broadcasting.connections.php', 'broadcasting.connections'); + $this->mergeConfigFrom(__DIR__ . '/../../config/fleetbase.php', 'fleetbase'); + $this->mergeConfigFrom(__DIR__ . '/../../config/auth.php', 'auth'); + $this->mergeConfigFrom(__DIR__ . '/../../config/sanctum.php', 'sanctum'); + $this->mergeConfigFrom(__DIR__ . '/../../config/twilio.php', 'twilio'); + $this->mergeConfigFrom(__DIR__ . '/../../config/webhook-server.php', 'webhook-server'); + $this->mergeConfigFrom(__DIR__ . '/../../config/permission.php', 'permission'); + $this->mergeConfigFrom(__DIR__ . '/../../config/activitylog.php', 'activitylog'); + $this->mergeConfigFrom(__DIR__ . '/../../config/excel.php', 'excel'); + $this->mergeConfigFromSettings(); + $this->addServerIpAsAllowedOrigin(); + } + + /** + * Merge configuration values from application settings. + * + * This function iterates through a predefined list of settings keys, + * retrieves their values from the system settings, and updates the + * Laravel configuration values accordingly. For some settings, it + * also updates corresponding environment variables. + * + * The settings keys and the corresponding config keys are defined + * in the $settings array. The $putsenv array defines the settings + * keys that also need to update environment variables and maps each + * settings key to the environment variables that need to be updated. + * + * @return void + */ + public function mergeConfigFromSettings() + { + try { + // Try to make a simple DB call + DB::connection()->getPdo(); + + // Check if the settings table exists + if (!Schema::hasTable('settings')) { + return; + } + + // Rest of your function code... + } catch (\Exception $e) { + // Connection failed, or other error occurred + return; + } + + $putsenv = [ + 'services.aws' => ['key' => 'AWS_ACCESS_KEY_ID', 'secret' => 'AWS_SECRET_ACCESS_KEY', 'region' => 'AWS_DEFAULT_REGION'], + 'services.google_maps' => ['api_key' => 'GOOGLE_MAPS_API_KEY', 'locale' => 'GOOGLE_MAPS_LOCALE'], + 'services.twilio' => ['sid' => 'TWILIO_SID', 'token' => 'TWILIO_TOKEN', 'from' => 'TWILIO_FROM'] + ]; + + $settings = [ + ['settingsKey' => 'filesystem.driver', 'configKey' => 'filesystems.default'], + ['settingsKey' => 'filesystem.s3', 'configKey' => 'filesystems.disks.s3'], + ['settingsKey' => 'mail.mailer', 'configKey' => 'mail.default'], + ['settingsKey' => 'mail.from', 'configKey' => 'mail.from'], + ['settingsKey' => 'mail.smtp', 'configKey' => 'mail.mailers.smtp'], + ['settingsKey' => 'queue.driver', 'configKey' => 'queue.default'], + ['settingsKey' => 'queue.sqs', 'configKey' => 'queue.connections.sqs'], + ['settingsKey' => 'queue.beanstalkd', 'configKey' => 'queue.connections.beanstalkd'], + ['settingsKey' => 'services.aws', 'configKey' => 'services.aws'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'queue.connections.sqs.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'queue.connections.sqs.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'queue.connections.sqs.region'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'cache.stores.dynamodb.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'cache.stores.dynamodb.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'cache.stores.dynamodb.region'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'filesystems.disks.s3.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'filesystems.disks.s3.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'filesystems.disks.s3.region'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'mail.mailers.ses.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'mail.mailers.ses.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'mail.mailers.ses.region'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'services.ses.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'services.ses.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'services.ses.region'], + ['settingsKey' => 'services.google_maps', 'configKey' => 'services.google_maps'], + ['settingsKey' => 'services.twilio', 'configKey' => 'services.twilio'], + ['settingsKey' => 'services.twilio', 'configKey' => 'twilio.connections.twilio'], + ['settingsKey' => 'services.ipinfo', 'configKey' => 'services.ipinfo'], + ['settingsKey' => 'services.ipinfo', 'configKey' => 'fleetbase.services.ipinfo'], + ]; + + foreach ($settings as $setting) { + $settingsKey = $setting['settingsKey']; + $configKey = $setting['configKey']; + $value = Setting::system($settingsKey); + + if ($value) { + // some settings should set env variables to be accessed throughout entire application + if (in_array($settingsKey, array_keys($putsenv))) { + $environmentVariables = $putsenv[$settingsKey]; + + foreach ($environmentVariables as $configEnvKey => $envKey) { + putenv($envKey . '="' . data_get($value, $configEnvKey) . '"'); + } + } + + // Fetch the current config array + $config = config()->all(); + + // Update the specific value in the config array + Arr::set($config, $configKey, $value); + + // Set the entire config array + config($config); + } + } + } + + /** + * Add the server's IP address to the CORS allowed origins. + * + * This function retrieves the server's IP address and adds it to the + * list of CORS allowed origins in the Laravel configuration. If the + * server's IP address is already in the list, the function doesn't + * add it again. + * + * @return void + */ + public function addServerIpAsAllowedOrigin() + { + $cacheKey = 'server_public_ip'; + $cacheExpirationMinutes = 60 * 60 * 24 * 30; + + // Check the cache first + $serverIp = Cache::get($cacheKey); + + // If not cached, fetch the IP and store it in the cache + if (!$serverIp) { + $serverIp = trim(shell_exec('dig +short myip.opendns.com @resolver1.opendns.com')); + + if (!$serverIp) { + return; + } + + Cache::put($cacheKey, $serverIp, $cacheExpirationMinutes); + } + + $allowedOrigins = config('cors.allowed_origins', []); + $serverIpOrigin = "http://{$serverIp}:4200"; + + if (!in_array($serverIpOrigin, $allowedOrigins, true)) { + $allowedOrigins[] = $serverIpOrigin; + } + + config(['cors.allowed_origins' => $allowedOrigins]); + } + + /** + * Registers all class extension macros from the specified path and namespace. + * + * @param string|null $from The path to load the macros from. If null, the default path is used. + * @param string|null $namespace The namespace to load the macros from. If null, the default namespaces are used. + * + * @return void + */ + public function registerExpansionsFrom($from = null, $namespace = null): void + { + if (is_array($from)) { + foreach ($from as $frm) { + $this->registerExpansionsFrom($frm); + } + + return; + } + + try { + $macros = new \DirectoryIterator($from ?? __DIR__ . '/../Expansions'); + } catch (\UnexpectedValueException $e) { + // no expansions + return; + } + + $packageNamespace = $this->findPackageNamespace($from); + + foreach ($macros as $macro) { + if (!$macro->isFile()) { + continue; + } + + $className = $macro->getBasename('.php'); + + if ($namespace === null) { + // resolve namespace + $namespaces = ['Fleetbase\\Expansions\\', 'Fleetbase\\Macros\\', 'Fleetbase\\Mixins\\']; + + if ($packageNamespace) { + $namespaces[] = $packageNamespace . '\\Expansions\\'; + $namespaces[] = $packageNamespace . '\\Macros\\'; + $namespaces[] = $packageNamespace . '\\Mixins\\'; + } + + $namespace = Arr::first( + $namespaces, + function ($ns) use ($className) { + return class_exists($ns . $className); + } + ); + + if (!$namespace) { + continue; + } + } + + $class = $namespace . $className; + $target = $class::target(); + + if (!class_exists($target)) { + continue; + } + + $method = $class::$method ?? Expansion::isExpandable($target) ? 'expand' : 'mixin'; + $target::$method(new $class); + } + } + + /** + * Register the middleware groups defined by the service provider. + * + * @return void + */ + public function registerMiddleware(): void + { + foreach ($this->middleware as $group => $middlewares) { + foreach ($middlewares as $middleware) { + $this->app->router->pushMiddlewareToGroup($group, $middleware); + } + } + } + + /** + * Register the model observers defined by the service provider. + * + * @return void + */ + public function registerObservers(): void + { + foreach ($this->observers as $model => $observer) { + $model::observe($observer); + } + } + + /** + * Load configuration files from the specified directory. + * + * @param string $path + * @return void + */ + protected function loadConfigFromDirectory($path) + { + $files = glob($path . '/*.php'); + + foreach ($files as $file) { + $this->mergeConfigFrom( + $file, + pathinfo($file, PATHINFO_FILENAME) + ); + } + } + + /** + * Register the console commands defined by the service provider. + * + * @return void + */ + public function registerCommands(): void + { + $this->commands($this->commands ?? []); + } + + /** + * Find the package namespace for a given path. + * + * @param string|null $path The path to search for the package namespace. If null, no namespace is returned. + * @return string|null The package namespace, or null if the path is not valid. + */ + private function findPackageNamespace($path = null): ?string + { + return Utils::findPackageNamespace($path); + } +} diff --git a/src/Console/Commands/MigrateSandbox.php b/src/Console/Commands/MigrateSandbox.php index 95cdfaa..cafeebf 100644 --- a/src/Console/Commands/MigrateSandbox.php +++ b/src/Console/Commands/MigrateSandbox.php @@ -47,9 +47,12 @@ public function handle() // only run core and fleetops migrations $paths = [ 'vendor/fleetbase/core-api/migrations', - 'vendor/fleetbase/fleetops-api/migrations' + // 'vendor/fleetbase/fleetops-api/migrations' ]; + $directories = Utils::getMigrationDirectories(); + dd($directories); + foreach ($paths as $path) { $this->call($command, [ '--seed' => $seed, diff --git a/src/Exceptions/FleetbaseRequestException.php b/src/Exceptions/FleetbaseRequestException.php index 39569c8..6149eb0 100644 --- a/src/Exceptions/FleetbaseRequestException.php +++ b/src/Exceptions/FleetbaseRequestException.php @@ -2,15 +2,12 @@ namespace Fleetbase\Exceptions; -use Throwable; -use Exception; - -class FleetbaseRequestValidationException extends Exception implements Throwable +class FleetbaseRequestException extends \Exception implements \Throwable { protected string $message = 'Invalid request'; protected array $errors = []; - public function __construct($errors = [], $message = 'Invalid request', $code = 0, Throwable $previous = null) + public function __construct($errors = [], $message = 'Invalid request', $code = 0, \Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->errors = $errors; diff --git a/src/Exceptions/FleetbaseRequestValidationException.php b/src/Exceptions/FleetbaseRequestValidationException.php new file mode 100644 index 0000000..b255d41 --- /dev/null +++ b/src/Exceptions/FleetbaseRequestValidationException.php @@ -0,0 +1,20 @@ +errors = $errors; + } + + public function getErrors() + { + return (array) $this->errors; + } +} \ No newline at end of file diff --git a/src/Expansions/PendingResourceRegistration.php b/src/Expansions/PendingResourceRegistration.php index 7683694..7dfa52c 100644 --- a/src/Expansions/PendingResourceRegistration.php +++ b/src/Expansions/PendingResourceRegistration.php @@ -16,6 +16,7 @@ public static function target() public function setRouter() { return function (Router $router) { + /** @var \Illuminate\Routing\PendingResourceRegistration $this */ $this->router = $router; return $this; @@ -25,6 +26,7 @@ public function setRouter() public function extend() { return function (?Closure $callback = null) { + /** @var \Illuminate\Routing\PendingResourceRegistration $this */ if ($this->router instanceof Router && is_callable($callback)) { $callback($this->router); } diff --git a/src/Models/User.php b/src/Models/User.php index f2e0a4a..3a08dde 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -25,7 +25,6 @@ use Fleetbase\Traits\HasCacheableAttributes; use Fleetbase\Traits\HasMetaAttributes; use Illuminate\Database\Eloquent\Concerns\HasTimestamps; -use Laravel\Cashier\Billable; class User extends Authenticatable { @@ -44,8 +43,7 @@ class User extends Authenticatable CausesActivity, SoftDeletes, Expandable, - Filterable, - Billable; + Filterable; /** * The database connection to use. diff --git a/src/Providers/CoreServiceProvider.php b/src/Providers/CoreServiceProvider.php index 29dd93a..0ff1675 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -2,358 +2,11 @@ namespace Fleetbase\Providers; -use Fleetbase\Models\Setting; -use Fleetbase\Support\Expansion; -use Fleetbase\Support\Utils; -use Laravel\Cashier\Cashier; -use Illuminate\Support\ServiceProvider; -use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Schema; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Cache; -use Illuminate\Http\Resources\Json\JsonResource; +use Fleetbase\Activate\CoreServiceProvider as ServiceProvider; /** - * CoreServiceProvider + * Backward compatibility service provicer. */ class CoreServiceProvider extends ServiceProvider { - /** - * The observers registered with the service provider. - * - * @var array - */ - public $observers = [ - \Fleetbase\Models\User::class => \Fleetbase\Observers\UserObserver::class, - \Fleetbase\Models\ApiCredential::class => \Fleetbase\Observers\ApiCredentialObserver::class, - \Spatie\Activitylog\Models\Activity::class => \Fleetbase\Observers\ActivityObserver::class, - ]; - - /** - * The middleware groups registered with the service provider. - * - * @var array - */ - public $middleware = [ - 'fleetbase.protected' => [ - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - 'auth:sanctum', - \Fleetbase\Http\Middleware\SetupFleetbaseSession::class - ] - ]; - - /** - * The console commands registered with the service provider. - * - * @var array - */ - public $commands = [ - \Fleetbase\Console\Commands\CreateDatabase::class, - \Fleetbase\Console\Commands\SeedDatabase::class, - \Fleetbase\Console\Commands\MigrateSandbox::class, - \Fleetbase\Console\Commands\InitializeSandboxKeyColumn::class, - \Fleetbase\Console\Commands\SyncSandbox::class, - \Fleetbase\Console\Commands\BackupDatabase\MysqlS3Backup::class - ]; - - /** - * Bootstrap any package services. - * - * @return void - */ - public function boot() - { - JsonResource::withoutWrapping(); - Cashier::ignoreMigrations(); - - $this->registerCommands(); - $this->registerObservers(); - $this->registerExpansionsFrom(); - $this->registerMiddleware(); - $this->loadRoutesFrom(__DIR__ . '/../routes.php'); - $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); - $this->mergeConfigFrom(__DIR__ . '/../../config/database.connections.php', 'database.connections'); - $this->mergeConfigFrom(__DIR__ . '/../../config/database.redis.php', 'database.redis'); - $this->mergeConfigFrom(__DIR__ . '/../../config/broadcasting.connections.php', 'broadcasting.connections'); - $this->mergeConfigFrom(__DIR__ . '/../../config/fleetbase.php', 'fleetbase'); - $this->mergeConfigFrom(__DIR__ . '/../../config/auth.php', 'auth'); - $this->mergeConfigFrom(__DIR__ . '/../../config/sanctum.php', 'sanctum'); - $this->mergeConfigFrom(__DIR__ . '/../../config/twilio.php', 'twilio'); - $this->mergeConfigFrom(__DIR__ . '/../../config/webhook-server.php', 'webhook-server'); - $this->mergeConfigFrom(__DIR__ . '/../../config/permission.php', 'permission'); - $this->mergeConfigFrom(__DIR__ . '/../../config/activitylog.php', 'activitylog'); - $this->mergeConfigFrom(__DIR__ . '/../../config/excel.php', 'excel'); - $this->mergeConfigFromSettings(); - $this->addServerIpAsAllowedOrigin(); - } - - /** - * Merge configuration values from application settings. - * - * This function iterates through a predefined list of settings keys, - * retrieves their values from the system settings, and updates the - * Laravel configuration values accordingly. For some settings, it - * also updates corresponding environment variables. - * - * The settings keys and the corresponding config keys are defined - * in the $settings array. The $putsenv array defines the settings - * keys that also need to update environment variables and maps each - * settings key to the environment variables that need to be updated. - * - * @return void - */ - public function mergeConfigFromSettings() - { - try { - // Try to make a simple DB call - DB::connection()->getPdo(); - - // Check if the settings table exists - if (!Schema::hasTable('settings')) { - return; - } - - // Rest of your function code... - } catch (\Exception $e) { - // Connection failed, or other error occurred - return; - } - - $putsenv = [ - 'services.aws' => ['key' => 'AWS_ACCESS_KEY_ID', 'secret' => 'AWS_SECRET_ACCESS_KEY', 'region' => 'AWS_DEFAULT_REGION'], - 'services.google_maps' => ['api_key' => 'GOOGLE_MAPS_API_KEY', 'locale' => 'GOOGLE_MAPS_LOCALE'], - 'services.twilio' => ['sid' => 'TWILIO_SID', 'token' => 'TWILIO_TOKEN', 'from' => 'TWILIO_FROM'] - ]; - - $settings = [ - ['settingsKey' => 'filesystem.driver', 'configKey' => 'filesystems.default'], - ['settingsKey' => 'filesystem.s3', 'configKey' => 'filesystems.disks.s3'], - ['settingsKey' => 'mail.mailer', 'configKey' => 'mail.default'], - ['settingsKey' => 'mail.from', 'configKey' => 'mail.from'], - ['settingsKey' => 'mail.smtp', 'configKey' => 'mail.mailers.smtp'], - ['settingsKey' => 'queue.driver', 'configKey' => 'queue.default'], - ['settingsKey' => 'queue.sqs', 'configKey' => 'queue.connections.sqs'], - ['settingsKey' => 'queue.beanstalkd', 'configKey' => 'queue.connections.beanstalkd'], - ['settingsKey' => 'services.aws', 'configKey' => 'services.aws'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'queue.connections.sqs.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'queue.connections.sqs.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'queue.connections.sqs.region'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'cache.stores.dynamodb.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'cache.stores.dynamodb.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'cache.stores.dynamodb.region'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'filesystems.disks.s3.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'filesystems.disks.s3.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'filesystems.disks.s3.region'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'mail.mailers.ses.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'mail.mailers.ses.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'mail.mailers.ses.region'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'services.ses.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'services.ses.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'services.ses.region'], - ['settingsKey' => 'services.google_maps', 'configKey' => 'services.google_maps'], - ['settingsKey' => 'services.twilio', 'configKey' => 'services.twilio'], - ['settingsKey' => 'services.twilio', 'configKey' => 'twilio.connections.twilio'], - ['settingsKey' => 'services.ipinfo', 'configKey' => 'services.ipinfo'], - ['settingsKey' => 'services.ipinfo', 'configKey' => 'fleetbase.services.ipinfo'], - ]; - - foreach ($settings as $setting) { - $settingsKey = $setting['settingsKey']; - $configKey = $setting['configKey']; - $value = Setting::system($settingsKey); - - if ($value) { - // some settings should set env variables to be accessed throughout entire application - if (in_array($settingsKey, array_keys($putsenv))) { - $environmentVariables = $putsenv[$settingsKey]; - - foreach ($environmentVariables as $configEnvKey => $envKey) { - putenv($envKey . '="' . data_get($value, $configEnvKey) . '"'); - } - } - - // Fetch the current config array - $config = config()->all(); - - // Update the specific value in the config array - Arr::set($config, $configKey, $value); - - // Set the entire config array - config($config); - } - } - } - - /** - * Add the server's IP address to the CORS allowed origins. - * - * This function retrieves the server's IP address and adds it to the - * list of CORS allowed origins in the Laravel configuration. If the - * server's IP address is already in the list, the function doesn't - * add it again. - * - * @return void - */ - public function addServerIpAsAllowedOrigin() - { - $cacheKey = 'server_public_ip'; - $cacheExpirationMinutes = 60 * 60 * 24 * 30; - - // Check the cache first - $serverIp = Cache::get($cacheKey); - - // If not cached, fetch the IP and store it in the cache - if (!$serverIp) { - $serverIp = trim(shell_exec('dig +short myip.opendns.com @resolver1.opendns.com')); - - if (!$serverIp) { - return; - } - - Cache::put($cacheKey, $serverIp, $cacheExpirationMinutes); - } - - $allowedOrigins = config('cors.allowed_origins', []); - $serverIpOrigin = "http://{$serverIp}:4200"; - - if (!in_array($serverIpOrigin, $allowedOrigins, true)) { - $allowedOrigins[] = $serverIpOrigin; - } - - config(['cors.allowed_origins' => $allowedOrigins]); - } - - /** - * Registers all class extension macros from the specified path and namespace. - * - * @param string|null $from The path to load the macros from. If null, the default path is used. - * @param string|null $namespace The namespace to load the macros from. If null, the default namespaces are used. - * - * @return void - */ - public function registerExpansionsFrom($from = null, $namespace = null): void - { - if (is_array($from)) { - foreach ($from as $frm) { - $this->registerExpansionsFrom($frm); - } - - return; - } - - try { - $macros = new \DirectoryIterator($from ?? __DIR__ . '/../Expansions'); - } catch (\UnexpectedValueException $e) { - // no expansions - return; - } - - $packageNamespace = $this->findPackageNamespace($from); - - foreach ($macros as $macro) { - if (!$macro->isFile()) { - continue; - } - - $className = $macro->getBasename('.php'); - - if ($namespace === null) { - // resolve namespace - $namespaces = ['Fleetbase\\Expansions\\', 'Fleetbase\\Macros\\', 'Fleetbase\\Mixins\\']; - - if ($packageNamespace) { - $namespaces[] = $packageNamespace . '\\Expansions\\'; - $namespaces[] = $packageNamespace . '\\Macros\\'; - $namespaces[] = $packageNamespace . '\\Mixins\\'; - } - - $namespace = Arr::first( - $namespaces, - function ($ns) use ($className) { - return class_exists($ns . $className); - } - ); - - if (!$namespace) { - continue; - } - } - - $class = $namespace . $className; - $target = $class::target(); - - if (!class_exists($target)) { - continue; - } - - $method = $class::$method ?? Expansion::isExpandable($target) ? 'expand' : 'mixin'; - $target::$method(new $class); - } - } - - /** - * Register the middleware groups defined by the service provider. - * - * @return void - */ - public function registerMiddleware(): void - { - foreach ($this->middleware as $group => $middlewares) { - foreach ($middlewares as $middleware) { - $this->app->router->pushMiddlewareToGroup($group, $middleware); - } - } - } - - /** - * Register the model observers defined by the service provider. - * - * @return void - */ - public function registerObservers(): void - { - foreach ($this->observers as $model => $observer) { - $model::observe($observer); - } - } - - /** - * Load configuration files from the specified directory. - * - * @param string $path - * @return void - */ - protected function loadConfigFromDirectory($path) - { - $files = glob($path . '/*.php'); - - foreach ($files as $file) { - $this->mergeConfigFrom( - $file, - pathinfo($file, PATHINFO_FILENAME) - ); - } - } - - /** - * Register the console commands defined by the service provider. - * - * @return void - */ - public function registerCommands(): void - { - $this->commands($this->commands ?? []); - } - - /** - * Find the package namespace for a given path. - * - * @param string|null $path The path to search for the package namespace. If null, no namespace is returned. - * @return string|null The package namespace, or null if the path is not valid. - */ - private function findPackageNamespace($path = null): ?string - { - return Utils::findPackageNamespace($path); - } } diff --git a/src/Support/Utils.php b/src/Support/Utils.php index d61b2fe..23b7b7a 100644 --- a/src/Support/Utils.php +++ b/src/Support/Utils.php @@ -1766,6 +1766,22 @@ public static function getInstalledFleetbaseExtensions() return static::findComposerPackagesWithKeyword('fleetbase-extension'); } + public static function getMigrationDirectories() + { + $packages = static::getInstalledFleetbaseExtensions(); + $directories = []; + + foreach ($packages as $packageName => $package) { + $migrationDirectory = base_path('vendor/' . $packageName . '/migrations/'); + + if (file_exists($migrationDirectory)) { + $directories[] = $migrationDirectory; + } + } + + return $directories; + } + /** * Get the namespaced names of the authentication schemas found in the installed Fleetbase extensions. * From 5d003c00d3380b9bfd26154ed0160769dad51778 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Sun, 23 Jul 2023 12:45:07 +0800 Subject: [PATCH 2/2] major update for building extensions --- composer.json | 4 +- ...023_04_25_094311_create_policies_table.php | 2 + seeds/PermissionSeeder.php | 20 +- src/Activate/CoreServiceProvider.php | 357 ------------------ src/Console/Commands/MigrateSandbox.php | 73 +++- src/Console/Commands/SeedDatabase.php | 16 +- src/Expansions/Response.php | 3 +- src/Models/Setting.php | 26 +- src/Models/User.php | 10 + src/Providers/CoreServiceProvider.php | 349 ++++++++++++++++- src/Support/Utils.php | 194 +++++++++- 11 files changed, 670 insertions(+), 384 deletions(-) delete mode 100644 src/Activate/CoreServiceProvider.php diff --git a/composer.json b/composer.json index e894ca9..fdc716d 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "fleetbase/core-api", - "version": "1.1.4-alpha", + "version": "1.1.5-alpha", "description": "Core Framework and Resources for Fleetbase API", "keywords": [ "fleetbase", @@ -66,7 +66,7 @@ "extra": { "laravel": { "providers": [ - "Fleetbase\\Activate\\CoreServiceProvider", + "Fleetbase\\Providers\\CoreServiceProvider", "Fleetbase\\Providers\\EventServiceProvider", "Fleetbase\\Providers\\SocketClusterServiceProvider" ] diff --git a/migrations/2023_04_25_094311_create_policies_table.php b/migrations/2023_04_25_094311_create_policies_table.php index 6b30ee9..2b4d3af 100644 --- a/migrations/2023_04_25_094311_create_policies_table.php +++ b/migrations/2023_04_25_094311_create_policies_table.php @@ -48,7 +48,9 @@ public function up() */ public function down() { + Schema::disableForeignKeyConstraints(); Schema::dropIfExists('policies'); Schema::dropIfExists('model_has_policies'); + Schema::enableForeignKeyConstraints(); } }; diff --git a/seeds/PermissionSeeder.php b/seeds/PermissionSeeder.php index a2c1f5f..46b61a5 100644 --- a/seeds/PermissionSeeder.php +++ b/seeds/PermissionSeeder.php @@ -55,11 +55,11 @@ public function run() try { $administratorPolicy->givePermissionTo($permission); } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { - dd($e->getMessage(), $guard, $permission, $administratorPolicy); + dd($e->getMessage()); } // output message for permissions creation - $this->output('Created (' . $guard . ') permission: ' . $permission->name); + // $this->output('Created (' . $guard . ') permission: ' . $permission->name); // check if schema has direct permissions to add if (is_array($permissions)) { @@ -79,11 +79,11 @@ public function run() try { $administratorPolicy->givePermissionTo($permission); } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { - dd($e->getMessage(), $guard, $permission, $administratorPolicy); + dd($e->getMessage()); } // output message for permissions creation - $this->output('Created (' . $guard . ') permission: ' . $permission->name); + // $this->output('Created (' . $guard . ') permission: ' . $permission->name); } } @@ -156,16 +156,16 @@ public function run() try { $fullAccessPolicy->givePermissionTo($permission); } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { - dd($e->getMessage(), $guard, $permission, $fullAccessPolicy); + dd($e->getMessage()); } try { $resourceFullAccessPolicy->givePermissionTo($permission); } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { - dd($e->getMessage(), $guard, $permission, $resourceFullAccessPolicy); + dd($e->getMessage()); } // output message for permissions creation - $this->output('Created (' . $guard . ') permission: ' . $permission->name); + // $this->output('Created (' . $guard . ') permission: ' . $permission->name); // create action permissions $resourceActions = array_merge($actions, data_get($resource, 'actions', [])); @@ -197,17 +197,17 @@ public function run() try { $readOnlyPolicy->givePermissionTo($permission); } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { - dd($e->getMessage(), $guard, $permission, $readOnlyPolicy); + dd($e->getMessage()); } try { $resourceReadOnlyPolicy->givePermissionTo($permission); } catch (\Spatie\Permission\Exceptions\GuardDoesNotMatch $e) { - dd($e->getMessage(), $guard, $permission, $resourceReadOnlyPolicy); + dd($e->getMessage()); } } // output message for permissions creation - $this->output('Created (' . $guard . ') permission: ' . $permission->name); + // $this->output('Created (' . $guard . ') permission: ' . $permission->name); } } } diff --git a/src/Activate/CoreServiceProvider.php b/src/Activate/CoreServiceProvider.php deleted file mode 100644 index 082f8c3..0000000 --- a/src/Activate/CoreServiceProvider.php +++ /dev/null @@ -1,357 +0,0 @@ - \Fleetbase\Observers\UserObserver::class, - \Fleetbase\Models\ApiCredential::class => \Fleetbase\Observers\ApiCredentialObserver::class, - \Spatie\Activitylog\Models\Activity::class => \Fleetbase\Observers\ActivityObserver::class, - ]; - - /** - * The middleware groups registered with the service provider. - * - * @var array - */ - public $middleware = [ - 'fleetbase.protected' => [ - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - 'auth:sanctum', - \Fleetbase\Http\Middleware\SetupFleetbaseSession::class - ] - ]; - - /** - * The console commands registered with the service provider. - * - * @var array - */ - public $commands = [ - \Fleetbase\Console\Commands\CreateDatabase::class, - \Fleetbase\Console\Commands\SeedDatabase::class, - \Fleetbase\Console\Commands\MigrateSandbox::class, - \Fleetbase\Console\Commands\InitializeSandboxKeyColumn::class, - \Fleetbase\Console\Commands\SyncSandbox::class, - \Fleetbase\Console\Commands\BackupDatabase\MysqlS3Backup::class - ]; - - /** - * Bootstrap any package services. - * - * @return void - */ - public function boot() - { - JsonResource::withoutWrapping(); - - $this->registerCommands(); - $this->registerObservers(); - $this->registerExpansionsFrom(); - $this->registerMiddleware(); - $this->loadRoutesFrom(__DIR__ . '/../routes.php'); - $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); - $this->mergeConfigFrom(__DIR__ . '/../../config/database.connections.php', 'database.connections'); - $this->mergeConfigFrom(__DIR__ . '/../../config/database.redis.php', 'database.redis'); - $this->mergeConfigFrom(__DIR__ . '/../../config/broadcasting.connections.php', 'broadcasting.connections'); - $this->mergeConfigFrom(__DIR__ . '/../../config/fleetbase.php', 'fleetbase'); - $this->mergeConfigFrom(__DIR__ . '/../../config/auth.php', 'auth'); - $this->mergeConfigFrom(__DIR__ . '/../../config/sanctum.php', 'sanctum'); - $this->mergeConfigFrom(__DIR__ . '/../../config/twilio.php', 'twilio'); - $this->mergeConfigFrom(__DIR__ . '/../../config/webhook-server.php', 'webhook-server'); - $this->mergeConfigFrom(__DIR__ . '/../../config/permission.php', 'permission'); - $this->mergeConfigFrom(__DIR__ . '/../../config/activitylog.php', 'activitylog'); - $this->mergeConfigFrom(__DIR__ . '/../../config/excel.php', 'excel'); - $this->mergeConfigFromSettings(); - $this->addServerIpAsAllowedOrigin(); - } - - /** - * Merge configuration values from application settings. - * - * This function iterates through a predefined list of settings keys, - * retrieves their values from the system settings, and updates the - * Laravel configuration values accordingly. For some settings, it - * also updates corresponding environment variables. - * - * The settings keys and the corresponding config keys are defined - * in the $settings array. The $putsenv array defines the settings - * keys that also need to update environment variables and maps each - * settings key to the environment variables that need to be updated. - * - * @return void - */ - public function mergeConfigFromSettings() - { - try { - // Try to make a simple DB call - DB::connection()->getPdo(); - - // Check if the settings table exists - if (!Schema::hasTable('settings')) { - return; - } - - // Rest of your function code... - } catch (\Exception $e) { - // Connection failed, or other error occurred - return; - } - - $putsenv = [ - 'services.aws' => ['key' => 'AWS_ACCESS_KEY_ID', 'secret' => 'AWS_SECRET_ACCESS_KEY', 'region' => 'AWS_DEFAULT_REGION'], - 'services.google_maps' => ['api_key' => 'GOOGLE_MAPS_API_KEY', 'locale' => 'GOOGLE_MAPS_LOCALE'], - 'services.twilio' => ['sid' => 'TWILIO_SID', 'token' => 'TWILIO_TOKEN', 'from' => 'TWILIO_FROM'] - ]; - - $settings = [ - ['settingsKey' => 'filesystem.driver', 'configKey' => 'filesystems.default'], - ['settingsKey' => 'filesystem.s3', 'configKey' => 'filesystems.disks.s3'], - ['settingsKey' => 'mail.mailer', 'configKey' => 'mail.default'], - ['settingsKey' => 'mail.from', 'configKey' => 'mail.from'], - ['settingsKey' => 'mail.smtp', 'configKey' => 'mail.mailers.smtp'], - ['settingsKey' => 'queue.driver', 'configKey' => 'queue.default'], - ['settingsKey' => 'queue.sqs', 'configKey' => 'queue.connections.sqs'], - ['settingsKey' => 'queue.beanstalkd', 'configKey' => 'queue.connections.beanstalkd'], - ['settingsKey' => 'services.aws', 'configKey' => 'services.aws'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'queue.connections.sqs.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'queue.connections.sqs.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'queue.connections.sqs.region'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'cache.stores.dynamodb.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'cache.stores.dynamodb.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'cache.stores.dynamodb.region'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'filesystems.disks.s3.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'filesystems.disks.s3.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'filesystems.disks.s3.region'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'mail.mailers.ses.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'mail.mailers.ses.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'mail.mailers.ses.region'], - ['settingsKey' => 'services.aws.key', 'configKey' => 'services.ses.key'], - ['settingsKey' => 'services.aws.secret', 'configKey' => 'services.ses.secret'], - ['settingsKey' => 'services.aws.region', 'configKey' => 'services.ses.region'], - ['settingsKey' => 'services.google_maps', 'configKey' => 'services.google_maps'], - ['settingsKey' => 'services.twilio', 'configKey' => 'services.twilio'], - ['settingsKey' => 'services.twilio', 'configKey' => 'twilio.connections.twilio'], - ['settingsKey' => 'services.ipinfo', 'configKey' => 'services.ipinfo'], - ['settingsKey' => 'services.ipinfo', 'configKey' => 'fleetbase.services.ipinfo'], - ]; - - foreach ($settings as $setting) { - $settingsKey = $setting['settingsKey']; - $configKey = $setting['configKey']; - $value = Setting::system($settingsKey); - - if ($value) { - // some settings should set env variables to be accessed throughout entire application - if (in_array($settingsKey, array_keys($putsenv))) { - $environmentVariables = $putsenv[$settingsKey]; - - foreach ($environmentVariables as $configEnvKey => $envKey) { - putenv($envKey . '="' . data_get($value, $configEnvKey) . '"'); - } - } - - // Fetch the current config array - $config = config()->all(); - - // Update the specific value in the config array - Arr::set($config, $configKey, $value); - - // Set the entire config array - config($config); - } - } - } - - /** - * Add the server's IP address to the CORS allowed origins. - * - * This function retrieves the server's IP address and adds it to the - * list of CORS allowed origins in the Laravel configuration. If the - * server's IP address is already in the list, the function doesn't - * add it again. - * - * @return void - */ - public function addServerIpAsAllowedOrigin() - { - $cacheKey = 'server_public_ip'; - $cacheExpirationMinutes = 60 * 60 * 24 * 30; - - // Check the cache first - $serverIp = Cache::get($cacheKey); - - // If not cached, fetch the IP and store it in the cache - if (!$serverIp) { - $serverIp = trim(shell_exec('dig +short myip.opendns.com @resolver1.opendns.com')); - - if (!$serverIp) { - return; - } - - Cache::put($cacheKey, $serverIp, $cacheExpirationMinutes); - } - - $allowedOrigins = config('cors.allowed_origins', []); - $serverIpOrigin = "http://{$serverIp}:4200"; - - if (!in_array($serverIpOrigin, $allowedOrigins, true)) { - $allowedOrigins[] = $serverIpOrigin; - } - - config(['cors.allowed_origins' => $allowedOrigins]); - } - - /** - * Registers all class extension macros from the specified path and namespace. - * - * @param string|null $from The path to load the macros from. If null, the default path is used. - * @param string|null $namespace The namespace to load the macros from. If null, the default namespaces are used. - * - * @return void - */ - public function registerExpansionsFrom($from = null, $namespace = null): void - { - if (is_array($from)) { - foreach ($from as $frm) { - $this->registerExpansionsFrom($frm); - } - - return; - } - - try { - $macros = new \DirectoryIterator($from ?? __DIR__ . '/../Expansions'); - } catch (\UnexpectedValueException $e) { - // no expansions - return; - } - - $packageNamespace = $this->findPackageNamespace($from); - - foreach ($macros as $macro) { - if (!$macro->isFile()) { - continue; - } - - $className = $macro->getBasename('.php'); - - if ($namespace === null) { - // resolve namespace - $namespaces = ['Fleetbase\\Expansions\\', 'Fleetbase\\Macros\\', 'Fleetbase\\Mixins\\']; - - if ($packageNamespace) { - $namespaces[] = $packageNamespace . '\\Expansions\\'; - $namespaces[] = $packageNamespace . '\\Macros\\'; - $namespaces[] = $packageNamespace . '\\Mixins\\'; - } - - $namespace = Arr::first( - $namespaces, - function ($ns) use ($className) { - return class_exists($ns . $className); - } - ); - - if (!$namespace) { - continue; - } - } - - $class = $namespace . $className; - $target = $class::target(); - - if (!class_exists($target)) { - continue; - } - - $method = $class::$method ?? Expansion::isExpandable($target) ? 'expand' : 'mixin'; - $target::$method(new $class); - } - } - - /** - * Register the middleware groups defined by the service provider. - * - * @return void - */ - public function registerMiddleware(): void - { - foreach ($this->middleware as $group => $middlewares) { - foreach ($middlewares as $middleware) { - $this->app->router->pushMiddlewareToGroup($group, $middleware); - } - } - } - - /** - * Register the model observers defined by the service provider. - * - * @return void - */ - public function registerObservers(): void - { - foreach ($this->observers as $model => $observer) { - $model::observe($observer); - } - } - - /** - * Load configuration files from the specified directory. - * - * @param string $path - * @return void - */ - protected function loadConfigFromDirectory($path) - { - $files = glob($path . '/*.php'); - - foreach ($files as $file) { - $this->mergeConfigFrom( - $file, - pathinfo($file, PATHINFO_FILENAME) - ); - } - } - - /** - * Register the console commands defined by the service provider. - * - * @return void - */ - public function registerCommands(): void - { - $this->commands($this->commands ?? []); - } - - /** - * Find the package namespace for a given path. - * - * @param string|null $path The path to search for the package namespace. If null, no namespace is returned. - * @return string|null The package namespace, or null if the path is not valid. - */ - private function findPackageNamespace($path = null): ?string - { - return Utils::findPackageNamespace($path); - } -} diff --git a/src/Console/Commands/MigrateSandbox.php b/src/Console/Commands/MigrateSandbox.php index cafeebf..b888206 100644 --- a/src/Console/Commands/MigrateSandbox.php +++ b/src/Console/Commands/MigrateSandbox.php @@ -45,13 +45,12 @@ public function handle() $command = $refresh ? 'migrate:refresh' : 'migrate'; // only run core and fleetops migrations - $paths = [ - 'vendor/fleetbase/core-api/migrations', - // 'vendor/fleetbase/fleetops-api/migrations' - ]; + $paths = ['vendor/fleetbase/core-api/migrations']; + $migrationDirectories = $this->getExtensionsMigrationPaths(); - $directories = Utils::getMigrationDirectories(); - dd($directories); + if (is_array($migrationDirectories)) { + $paths = array_merge($paths, $migrationDirectories); + } foreach ($paths as $path) { $this->call($command, [ @@ -62,4 +61,66 @@ public function handle() ]); } } + + /** + * Returns the relative paths to the migration directories of all installed Fleetbase extensions. + * + * This function retrieves all installed Fleetbase extensions, and then for each extension, + * it checks if sandbox migrations are disabled. If not, it gets the migration directory + * for the extension. All collected paths are then converted to relative paths and returned. + * + * @return array An array of the relative paths to the migration directories of all installed Fleetbase extensions. + */ + private function getExtensionsMigrationPaths(): array + { + $packages = Utils::getInstalledFleetbaseExtensions(); + $paths = []; + + foreach ($packages as $packageName => $package) { + // check if migrations is disabled for sandbox + $sandboxMigrations = Utils::getFleetbaseExtensionProperty($packageName, 'sandbox-migrations'); + + if ($sandboxMigrations === false || $sandboxMigrations === 'false' || $sandboxMigrations === 0 || $sandboxMigrations === '0') { + continue; + } + + $path = Utils::getMigrationDirectoryForExtension($packageName); + + if ($path) { + $paths[] = $path; + } + } + + return $this->makePathsRelative($paths); + } + + /** + * Converts an array of absolute paths to relative paths. + * + * This function maps over an array of paths and for each path, it creates a substring + * from the position of 'vendor' to the end of the string, effectively creating a relative path. + * The trailing slash is also removed from each path. If the provided input is not an array, + * the function will return an empty array. + * + * @param array|null $paths An array of absolute paths that will be converted to relative paths. + * @return array An array of relative paths. + */ + private function makePathsRelative(?array $paths = []): array + { + if (!is_array($paths)) { + return []; + } + + $relativePaths = array_map(function ($path) { + // Find the position of "vendor" in the string + $startPosition = strpos($path, 'vendor'); + // Create a substring from the position of "vendor" to the end of the string + $relativePath = substr($path, $startPosition); + // Remove the trailing slash + $relativePath = rtrim($relativePath, '/'); + return $relativePath; + }, $paths); + + return $relativePaths; + } } diff --git a/src/Console/Commands/SeedDatabase.php b/src/Console/Commands/SeedDatabase.php index fa56c78..4bed921 100644 --- a/src/Console/Commands/SeedDatabase.php +++ b/src/Console/Commands/SeedDatabase.php @@ -2,6 +2,7 @@ namespace Fleetbase\Console\Commands; +use Fleetbase\Support\Utils; use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; @@ -12,7 +13,7 @@ class SeedDatabase extends Command * * @var string */ - protected $signature = 'fleetbase:seed {--class=FleetbaseSeeder}'; + protected $signature = 'fleetbase:seed {--class}'; /** * The console command description. @@ -45,6 +46,19 @@ public function handle() '--class' => 'Fleetbase\\Seeds\\FleetbaseSeeder', ] ); + + // seed for extensions + $extensionSeeders = Utils::getSeedersFromExtensions(); + + foreach ($extensionSeeders as $seeder) { + // Manually include the seeder file + require_once $seeder['path']; + + // Instantiate the seeder class and run it + $seederInstance = new $seeder['class'](); + $seederInstance->run(); + } + $this->info('Fleetbase Seeders were run Successfully!'); } } diff --git a/src/Expansions/Response.php b/src/Expansions/Response.php index 6350f4e..f7d0475 100644 --- a/src/Expansions/Response.php +++ b/src/Expansions/Response.php @@ -35,7 +35,8 @@ public function error() if ($error instanceof MessageBag) { $error = $error->all(); } - + + /** @var \Illuminate\Support\Facades\Response $this */ return static::json( [ 'errors' => is_array($error) ? $error : [$error], diff --git a/src/Models/Setting.php b/src/Models/Setting.php index ac9ad43..d481447 100644 --- a/src/Models/Setting.php +++ b/src/Models/Setting.php @@ -147,13 +147,31 @@ public static function configure($key, $value = null) ); } + /** + * lookup a setting and return the value. + * + * @param string $key + * @param mixed $defaultValue + * @return mixed|null + */ + public static function lookup(string $key, $defaultValue = null) + { + $setting = static::where('key', $key)->first(); + + if (!$setting) { + return $defaultValue; + } + + return data_get($setting, 'value', $defaultValue); + } + public static function getBranding() { $brandingSettings = ['id' => 1, 'uuid' => 1]; - $iconUrl = static::where('key', 'branding.icon_url')->first(); - $logoUrl = static::where('key', 'branding.logo_url')->first(); - $brandingSettings['icon_url'] = $iconUrl ? $iconUrl->value : '/images/icon.png'; - $brandingSettings['logo_url'] = $logoUrl ? $logoUrl->value : '/images/fleetbase-logo-svg.svg'; + $iconUrl = static::where('key', 'branding.icon_url')->first(); + $logoUrl = static::where('key', 'branding.logo_url')->first(); + $brandingSettings['icon_url'] = $iconUrl ? $iconUrl->value : '/images/icon.png'; + $brandingSettings['logo_url'] = $logoUrl ? $logoUrl->value : '/images/fleetbase-logo-svg.svg'; return $brandingSettings; } diff --git a/src/Models/User.php b/src/Models/User.php index 3a08dde..ebfa512 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -356,6 +356,16 @@ public function isAdmin() return $this->type === 'admin'; } + /** + * Checks if the user is NOT admin + * + * @return boolean + */ + public function isNotAdmin() + { + return $this->type !== 'admin'; + } + /** * Adds a boolean dynamic property to check if user is an admin. * diff --git a/src/Providers/CoreServiceProvider.php b/src/Providers/CoreServiceProvider.php index 0ff1675..d657b74 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -2,11 +2,356 @@ namespace Fleetbase\Providers; -use Fleetbase\Activate\CoreServiceProvider as ServiceProvider; +use Fleetbase\Models\Setting; +use Fleetbase\Support\Expansion; +use Fleetbase\Support\Utils; +use Illuminate\Support\ServiceProvider; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Cache; +use Illuminate\Http\Resources\Json\JsonResource; /** - * Backward compatibility service provicer. + * CoreServiceProvider */ class CoreServiceProvider extends ServiceProvider { + /** + * The observers registered with the service provider. + * + * @var array + */ + public $observers = [ + \Fleetbase\Models\User::class => \Fleetbase\Observers\UserObserver::class, + \Fleetbase\Models\ApiCredential::class => \Fleetbase\Observers\ApiCredentialObserver::class, + \Spatie\Activitylog\Models\Activity::class => \Fleetbase\Observers\ActivityObserver::class, + ]; + + /** + * The middleware groups registered with the service provider. + * + * @var array + */ + public $middleware = [ + 'fleetbase.protected' => [ + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + 'auth:sanctum', + \Fleetbase\Http\Middleware\SetupFleetbaseSession::class + ] + ]; + + /** + * The console commands registered with the service provider. + * + * @var array + */ + public $commands = [ + \Fleetbase\Console\Commands\CreateDatabase::class, + \Fleetbase\Console\Commands\SeedDatabase::class, + \Fleetbase\Console\Commands\MigrateSandbox::class, + \Fleetbase\Console\Commands\InitializeSandboxKeyColumn::class, + \Fleetbase\Console\Commands\SyncSandbox::class, + \Fleetbase\Console\Commands\BackupDatabase\MysqlS3Backup::class + ]; + + /** + * Bootstrap any package services. + * + * @return void + */ + public function boot() + { + JsonResource::withoutWrapping(); + + $this->registerCommands(); + $this->registerObservers(); + $this->registerExpansionsFrom(); + $this->registerMiddleware(); + $this->loadRoutesFrom(__DIR__ . '/../routes.php'); + $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); + $this->mergeConfigFrom(__DIR__ . '/../../config/database.connections.php', 'database.connections'); + $this->mergeConfigFrom(__DIR__ . '/../../config/database.redis.php', 'database.redis'); + $this->mergeConfigFrom(__DIR__ . '/../../config/broadcasting.connections.php', 'broadcasting.connections'); + $this->mergeConfigFrom(__DIR__ . '/../../config/fleetbase.php', 'fleetbase'); + $this->mergeConfigFrom(__DIR__ . '/../../config/auth.php', 'auth'); + $this->mergeConfigFrom(__DIR__ . '/../../config/sanctum.php', 'sanctum'); + $this->mergeConfigFrom(__DIR__ . '/../../config/twilio.php', 'twilio'); + $this->mergeConfigFrom(__DIR__ . '/../../config/webhook-server.php', 'webhook-server'); + $this->mergeConfigFrom(__DIR__ . '/../../config/permission.php', 'permission'); + $this->mergeConfigFrom(__DIR__ . '/../../config/activitylog.php', 'activitylog'); + $this->mergeConfigFrom(__DIR__ . '/../../config/excel.php', 'excel'); + $this->mergeConfigFromSettings(); + $this->addServerIpAsAllowedOrigin(); + } + + /** + * Merge configuration values from application settings. + * + * This function iterates through a predefined list of settings keys, + * retrieves their values from the system settings, and updates the + * Laravel configuration values accordingly. For some settings, it + * also updates corresponding environment variables. + * + * The settings keys and the corresponding config keys are defined + * in the $settings array. The $putsenv array defines the settings + * keys that also need to update environment variables and maps each + * settings key to the environment variables that need to be updated. + * + * @return void + */ + public function mergeConfigFromSettings() + { + try { + // Try to make a simple DB call + DB::connection()->getPdo(); + + // Check if the settings table exists + if (!Schema::hasTable('settings')) { + return; + } + + // Rest of your function code... + } catch (\Exception $e) { + // Connection failed, or other error occurred + return; + } + + $putsenv = [ + 'services.aws' => ['key' => 'AWS_ACCESS_KEY_ID', 'secret' => 'AWS_SECRET_ACCESS_KEY', 'region' => 'AWS_DEFAULT_REGION'], + 'services.google_maps' => ['api_key' => 'GOOGLE_MAPS_API_KEY', 'locale' => 'GOOGLE_MAPS_LOCALE'], + 'services.twilio' => ['sid' => 'TWILIO_SID', 'token' => 'TWILIO_TOKEN', 'from' => 'TWILIO_FROM'] + ]; + + $settings = [ + ['settingsKey' => 'filesystem.driver', 'configKey' => 'filesystems.default'], + ['settingsKey' => 'filesystem.s3', 'configKey' => 'filesystems.disks.s3'], + ['settingsKey' => 'mail.mailer', 'configKey' => 'mail.default'], + ['settingsKey' => 'mail.from', 'configKey' => 'mail.from'], + ['settingsKey' => 'mail.smtp', 'configKey' => 'mail.mailers.smtp'], + ['settingsKey' => 'queue.driver', 'configKey' => 'queue.default'], + ['settingsKey' => 'queue.sqs', 'configKey' => 'queue.connections.sqs'], + ['settingsKey' => 'queue.beanstalkd', 'configKey' => 'queue.connections.beanstalkd'], + ['settingsKey' => 'services.aws', 'configKey' => 'services.aws'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'queue.connections.sqs.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'queue.connections.sqs.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'queue.connections.sqs.region'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'cache.stores.dynamodb.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'cache.stores.dynamodb.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'cache.stores.dynamodb.region'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'filesystems.disks.s3.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'filesystems.disks.s3.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'filesystems.disks.s3.region'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'mail.mailers.ses.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'mail.mailers.ses.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'mail.mailers.ses.region'], + ['settingsKey' => 'services.aws.key', 'configKey' => 'services.ses.key'], + ['settingsKey' => 'services.aws.secret', 'configKey' => 'services.ses.secret'], + ['settingsKey' => 'services.aws.region', 'configKey' => 'services.ses.region'], + ['settingsKey' => 'services.google_maps', 'configKey' => 'services.google_maps'], + ['settingsKey' => 'services.twilio', 'configKey' => 'services.twilio'], + ['settingsKey' => 'services.twilio', 'configKey' => 'twilio.connections.twilio'], + ['settingsKey' => 'services.ipinfo', 'configKey' => 'services.ipinfo'], + ['settingsKey' => 'services.ipinfo', 'configKey' => 'fleetbase.services.ipinfo'], + ]; + + foreach ($settings as $setting) { + $settingsKey = $setting['settingsKey']; + $configKey = $setting['configKey']; + $value = Setting::system($settingsKey); + + if ($value) { + // some settings should set env variables to be accessed throughout entire application + if (in_array($settingsKey, array_keys($putsenv))) { + $environmentVariables = $putsenv[$settingsKey]; + + foreach ($environmentVariables as $configEnvKey => $envKey) { + putenv($envKey . '="' . data_get($value, $configEnvKey) . '"'); + } + } + + // Fetch the current config array + $config = config()->all(); + + // Update the specific value in the config array + Arr::set($config, $configKey, $value); + + // Set the entire config array + config($config); + } + } + } + + /** + * Add the server's IP address to the CORS allowed origins. + * + * This function retrieves the server's IP address and adds it to the + * list of CORS allowed origins in the Laravel configuration. If the + * server's IP address is already in the list, the function doesn't + * add it again. + * + * @return void + */ + public function addServerIpAsAllowedOrigin() + { + $cacheKey = 'server_public_ip'; + $cacheExpirationMinutes = 60 * 60 * 24 * 30; + + // Check the cache first + $serverIp = Cache::get($cacheKey); + + // If not cached, fetch the IP and store it in the cache + if (!$serverIp) { + $serverIp = trim(shell_exec('dig +short myip.opendns.com @resolver1.opendns.com')); + + if (!$serverIp) { + return; + } + + Cache::put($cacheKey, $serverIp, $cacheExpirationMinutes); + } + + $allowedOrigins = config('cors.allowed_origins', []); + $serverIpOrigin = "http://{$serverIp}:4200"; + + if (!in_array($serverIpOrigin, $allowedOrigins, true)) { + $allowedOrigins[] = $serverIpOrigin; + } + + config(['cors.allowed_origins' => $allowedOrigins]); + } + + /** + * Registers all class extension macros from the specified path and namespace. + * + * @param string|null $from The path to load the macros from. If null, the default path is used. + * @param string|null $namespace The namespace to load the macros from. If null, the default namespaces are used. + * + * @return void + */ + public function registerExpansionsFrom($from = null, $namespace = null): void + { + if (is_array($from)) { + foreach ($from as $frm) { + $this->registerExpansionsFrom($frm); + } + + return; + } + + try { + $macros = new \DirectoryIterator($from ?? __DIR__ . '/../Expansions'); + } catch (\UnexpectedValueException $e) { + // no expansions + return; + } + + $packageNamespace = $this->findPackageNamespace($from); + + foreach ($macros as $macro) { + if (!$macro->isFile()) { + continue; + } + + $className = $macro->getBasename('.php'); + + if ($namespace === null) { + // resolve namespace + $namespaces = ['Fleetbase\\Expansions\\', 'Fleetbase\\Macros\\', 'Fleetbase\\Mixins\\']; + + if ($packageNamespace) { + $namespaces[] = $packageNamespace . '\\Expansions\\'; + $namespaces[] = $packageNamespace . '\\Macros\\'; + $namespaces[] = $packageNamespace . '\\Mixins\\'; + } + + $namespace = Arr::first( + $namespaces, + function ($ns) use ($className) { + return class_exists($ns . $className); + } + ); + + if (!$namespace) { + continue; + } + } + + $class = $namespace . $className; + $target = $class::target(); + + if (!class_exists($target)) { + continue; + } + + $method = $class::$method ?? Expansion::isExpandable($target) ? 'expand' : 'mixin'; + $target::$method(new $class); + } + } + + /** + * Register the middleware groups defined by the service provider. + * + * @return void + */ + public function registerMiddleware(): void + { + foreach ($this->middleware as $group => $middlewares) { + foreach ($middlewares as $middleware) { + $this->app->router->pushMiddlewareToGroup($group, $middleware); + } + } + } + + /** + * Register the model observers defined by the service provider. + * + * @return void + */ + public function registerObservers(): void + { + foreach ($this->observers as $model => $observer) { + $model::observe($observer); + } + } + + /** + * Load configuration files from the specified directory. + * + * @param string $path + * @return void + */ + protected function loadConfigFromDirectory($path) + { + $files = glob($path . '/*.php'); + + foreach ($files as $file) { + $this->mergeConfigFrom( + $file, + pathinfo($file, PATHINFO_FILENAME) + ); + } + } + + /** + * Register the console commands defined by the service provider. + * + * @return void + */ + public function registerCommands(): void + { + $this->commands($this->commands ?? []); + } + + /** + * Find the package namespace for a given path. + * + * @param string|null $path The path to search for the package namespace. If null, no namespace is returned. + * @return string|null The package namespace, or null if the path is not valid. + */ + private function findPackageNamespace($path = null): ?string + { + return Utils::findPackageNamespace($path); + } } diff --git a/src/Support/Utils.php b/src/Support/Utils.php index 23b7b7a..daf33a8 100644 --- a/src/Support/Utils.php +++ b/src/Support/Utils.php @@ -1665,6 +1665,43 @@ public static function fromFleetbaseExtensions(string $key): array return array_values($fleetbaseExtensions); } + /** + * Retrieves the values of a specified key from the "extra" property for a specific package + * with the "fleetbase" key. + * + * @param string $extension The fleetbase extension to lookup the property for. + * @param string $key The key to search for in the "extra" property of packages with the "fleetbase" key. + * @return mixed The value of the key + * + * @throws \RuntimeException If the installed.json file cannot be found. + */ + public static function getFleetbaseExtensionProperty(string $extension, string $key) + { + $installedJsonPath = realpath(base_path('vendor/composer/installed.json')); + + if (!$installedJsonPath) { + throw new \RuntimeException('Unable to find the installed.json file.'); + } + + $installedPackages = json_decode(file_get_contents($installedJsonPath), true); + $value = null; + + if (isset($installedPackages['packages'])) { + foreach ($installedPackages['packages'] as $package) { + if ($package['name'] !== $extension) { + continue; + } + + if (isset($package['extra']['fleetbase']) && isset($package['extra']['fleetbase'][$key])) { + $value = $package['extra']['fleetbase'][$key]; + break; + } + } + } + + return $value; + } + /** * Retrieves the database name for the Fleetbase connection from the configuration. * @@ -1766,7 +1803,132 @@ public static function getInstalledFleetbaseExtensions() return static::findComposerPackagesWithKeyword('fleetbase-extension'); } - public static function getMigrationDirectories() + /** + * Retrieves directories containing seeders from installed Fleetbase extensions. + * + * This function first gets all installed Fleetbase extensions. Then it iterates over them + * and checks for a 'seeds' directory within each one. If it exists, the directory path + * is added to an array. The function finally returns this array of migration directories. + * + * @return array The array containing the paths to seeder directories of all installed Fleetbase extensions. + * + * @throws \RuntimeException if an error occurs during directory retrieval. + */ + public static function getSeederClassesFromExtensions(): array + { + $packages = static::getInstalledFleetbaseExtensions(); + $seederClasses = []; + + foreach ($packages as $packageName => $package) { + // Derive the seeds directory path + $seedsDirectory = base_path('vendor/' . $packageName . '/seeds'); + + // Check if the seeds directory exists + if (!is_dir($seedsDirectory)) { + continue; + } + + // Get all PHP files in the seeds directory + $files = glob($seedsDirectory . '/*.php'); + + // Find the namespace that corresponds to the seeds directory + $namespace = static::getNamespaceFromAutoload($package['autoload']['psr-4'], 'seeds'); + + foreach ($files as $file) { + // Get the base name of the file, and remove the .php extension to get the class name + $className = basename($file, '.php'); + + // Combine the namespace and class name to get the fully qualified class name + $seederClasses[] = $namespace . '\\' . $className; + } + } + + return $seederClasses; + } + + /** + * Get directories containing seed files from Fleetbase extensions installed in the project. + * + * This method iterates over all packages installed in the project and identified as Fleetbase extensions. + * For each extension, it identifies the "seeds" directory, fetches all PHP files in it, and maps these files + * to their fully qualified class names based on the PSR-4 autoload configuration in the extension's composer.json. + * The resulting array contains the fully qualified class names and the full paths to the corresponding PHP files. + * + * @return array Each item is an associative array with two keys: + * 'class' => the fully qualified class name of a seeder, + * 'path' => the full path to the PHP file of the seeder. + * + * @throws \Exception if the composer.lock file does not exist or does not contain packages data. + */ + public static function getSeedersFromExtensions(): array + { + $packages = static::getInstalledFleetbaseExtensions(); + $seederClasses = []; + + foreach ($packages as $packageName => $package) { + // Derive the seeds directory path + $seedsDirectory = base_path('vendor/' . $packageName . '/seeds'); + + // Check if the seeds directory exists + if (!is_dir($seedsDirectory)) { + continue; + } + + // Get all PHP files in the seeds directory + $files = glob($seedsDirectory . '/*.php'); + + // Find the namespace that corresponds to the seeds directory + $namespace = static::getNamespaceFromAutoload($package['autoload']['psr-4'], 'seeds'); + + foreach ($files as $file) { + // Get the base name of the file, and remove the .php extension to get the class name + $className = basename($file, '.php'); + + // Combine the namespace and class name to get the fully qualified class name + $seederClasses[] = [ + 'class' => $namespace . '\\' . $className, + 'path' => $file, + ]; + } + } + + return $seederClasses; + } + + /** + * Determines the namespace of a given directory from a given PSR-4 autoload configuration. + * + * @param array $psr4 The PSR-4 autoload configuration, mapping namespace prefixes to directories. + * @param string $directory The directory whose corresponding namespace should be returned. + * + * @return string|null The namespace corresponding to the given directory in the autoload configuration, + * or null if no such namespace exists. + */ + private static function getNamespaceFromAutoload(array $psr4, string $directory): ?string + { + foreach ($psr4 as $namespace => $path) { + if (strpos($path, $directory) !== false) { + // Remove trailing backslashes from the namespace + $namespace = rtrim($namespace, '\\'); + return $namespace; + } + } + + return null; + } + + /** + * Retrieves directories containing migrations from installed Fleetbase extensions. + * + * This function first gets all installed Fleetbase extensions. Then it iterates over them + * and checks for a 'migrations' directory within each one. If it exists, the directory path + * is added to an array. The function finally returns this array of migration directories. + * + * @return array The array containing the paths to migration directories of all installed Fleetbase extensions. + * + * @throws \RuntimeException if an error occurs during directory retrieval. + */ + public static function getMigrationDirectories(): array { $packages = static::getInstalledFleetbaseExtensions(); $directories = []; @@ -1782,6 +1944,36 @@ public static function getMigrationDirectories() return $directories; } + /** + * Retrieves the migration directory for a specific Fleetbase extension. + * + * This function first gets all installed Fleetbase extensions. Then it iterates over them + * until it finds the specified extension. If the extension is found, the function constructs + * the path to its 'migrations' directory and returns this path. + * + * @param string $extension The name of the Fleetbase extension for which the migration directory is to be retrieved. + * + * @return string|null The path to the migration directory of the specified Fleetbase extension, or null if the extension is not found. + * + * @throws \RuntimeException if an error occurs during directory retrieval. + */ + public static function getMigrationDirectoryForExtension(string $extension): ?string + { + $packages = static::getInstalledFleetbaseExtensions(); + $migrationDirectory = null; + + foreach ($packages as $packageName => $package) { + if ($packageName !== $extension) { + continue; + } + + $migrationDirectory = base_path('vendor/' . $packageName . '/migrations/'); + break; + } + + return $migrationDirectory; + } + /** * Get the namespaced names of the authentication schemas found in the installed Fleetbase extensions. *