diff --git a/composer.json b/composer.json index 5ffe5db..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", @@ -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", 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/Console/Commands/MigrateSandbox.php b/src/Console/Commands/MigrateSandbox.php index 95cdfaa..b888206 100644 --- a/src/Console/Commands/MigrateSandbox.php +++ b/src/Console/Commands/MigrateSandbox.php @@ -45,10 +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(); + + if (is_array($migrationDirectories)) { + $paths = array_merge($paths, $migrationDirectories); + } foreach ($paths as $path) { $this->call($command, [ @@ -59,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/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/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 f2e0a4a..ebfa512 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. @@ -358,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 29dd93a..d657b74 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -5,7 +5,6 @@ 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; @@ -65,7 +64,6 @@ class CoreServiceProvider extends ServiceProvider public function boot() { JsonResource::withoutWrapping(); - Cashier::ignoreMigrations(); $this->registerCommands(); $this->registerObservers(); diff --git a/src/Support/Utils.php b/src/Support/Utils.php index d61b2fe..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,6 +1803,177 @@ public static function getInstalledFleetbaseExtensions() return static::findComposerPackagesWithKeyword('fleetbase-extension'); } + /** + * 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 = []; + + foreach ($packages as $packageName => $package) { + $migrationDirectory = base_path('vendor/' . $packageName . '/migrations/'); + + if (file_exists($migrationDirectory)) { + $directories[] = $migrationDirectory; + } + } + + 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. *