diff --git a/src/Http/Controllers/Internal/v1/SettingController.php b/src/Http/Controllers/Internal/v1/SettingController.php index 8834410..905ba1a 100644 --- a/src/Http/Controllers/Internal/v1/SettingController.php +++ b/src/Http/Controllers/Internal/v1/SettingController.php @@ -11,6 +11,7 @@ use Illuminate\Http\Request; use Illuminate\Notifications\AnonymousNotifiable; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; @@ -89,9 +90,9 @@ public function saveFilesystemConfig(AdminRequest $request) $s3 = $request->input('s3', config('filesystems.disks.s3')); $gcs = static::_getGcsConfigWithIcomingRequest($request); - Setting::configure('system.filesystem.driver', $driver); - Setting::configure('system.filesystem.gcs', $gcs); - Setting::configure('system.filesystem.s3', array_merge(config('filesystems.disks.s3', []), $s3)); + Setting::configureSystem('filesystem.driver', $driver); + Setting::configureSystem('filesystem.gcs', $gcs); + Setting::configureSystem('filesystem.s3', array_merge(config('filesystems.disks.s3', []), $s3)); return response()->json(['status' => 'OK']); } @@ -172,7 +173,7 @@ public function testFilesystemConfig(AdminRequest $request) // set config values from input config(['filesystems.default' => $disk]); try { - $uploaded = Storage::disk($disk)->put('testfile.txt', 'Hello World'); + Storage::disk($disk)->put('testfile.txt', 'Hello World'); } catch (\Aws\S3\Exception\S3Exception $e) { $message = $e->getMessage(); $status = 'error'; @@ -181,6 +182,14 @@ public function testFilesystemConfig(AdminRequest $request) $status = 'error'; } + // confirm file uploaded + try { + $uploaded = Storage::disk($disk)->exists('testfile.txt'); + } catch (\Throwable $e) { + $message = $e->getMessage(); + $status = 'error'; + } + // sometimes there is no error but file is not uploaded if (!$uploaded) { $status = 'error'; @@ -231,9 +240,9 @@ public function saveMailConfig(AdminRequest $request) $from = $request->input('from', []); $smtp = $request->input('smtp', []); - Setting::configure('system.mail.mailer', $mailer); - Setting::configure('system.mail.from', $from); - Setting::configure('system.mail.smtp', array_merge(['transport' => 'smtp'], $smtp)); + Setting::configureSystem('mail.mailer', $mailer); + Setting::configureSystem('mail.from', $from); + Setting::configureSystem('mail.smtp', array_merge(['transport' => 'smtp'], $smtp)); return response()->json(['status' => 'OK']); } @@ -269,13 +278,7 @@ public function testMailConfig(AdminRequest $request) try { Mail::send(new \Fleetbase\Mail\TestMail($user)); - } catch (\Aws\Ses\Exception\SesException $e) { - $message = $e->getMessage(); - $status = 'error'; - } catch (\Swift_TransportException $e) { - $message = $e->getMessage(); - $status = 'error'; - } catch (\Throwable $e) { + } catch (\Aws\Ses\Exception\SesException|\Exception $e) { $message = $e->getMessage(); $status = 'error'; } @@ -322,9 +325,9 @@ public function saveQueueConfig(AdminRequest $request) $sqs = $request->input('sqs', config('queue.connections.sqs')); $beanstalkd = $request->input('beanstalkd', config('queue.connections.beanstalkd')); - Setting::configure('system.queue.driver', $driver); - Setting::configure('system.queue.sqs', array_merge(config('queue.connections.sqs'), $sqs)); - Setting::configure('system.queue.beanstalkd', array_merge(config('queue.connections.beanstalkd'), $beanstalkd)); + Setting::configureSystem('queue.driver', $driver); + Setting::configureSystem('queue.sqs', array_merge(config('queue.connections.sqs'), $sqs)); + Setting::configureSystem('queue.beanstalkd', array_merge(config('queue.connections.beanstalkd'), $beanstalkd)); return response()->json(['status' => 'OK']); } @@ -346,17 +349,11 @@ public function testQueueConfig(AdminRequest $request) config(['queue.default' => $queue]); try { - \Illuminate\Support\Facades\Queue::pushRaw(new \Illuminate\Support\MessageBag(['Hello World'])); + Queue::pushRaw(json_encode(['message' => 'Hello World'])); } catch (\Aws\Sqs\Exception\SqsException $e) { $message = $e->getMessage(); $status = 'error'; - } catch (\Exception $e) { - $message = $e->getMessage(); - $status = 'error'; - } catch (\Error $e) { - $message = $e->getMessage(); - $status = 'error'; - } catch (\ErrorException $e) { + } catch (\Throwable $e) { $message = $e->getMessage(); $status = 'error'; } @@ -418,11 +415,11 @@ public function saveServicesConfig(AdminRequest $request) $twilio = $request->input('twilio', config('services.twilio')); $sentry = $request->input('sentry', config('sentry.dsn')); - Setting::configure('system.services.aws', array_merge(config('services.aws', []), $aws)); - Setting::configure('system.services.ipinfo', array_merge(config('services.ipinfo', []), $ipinfo)); - Setting::configure('system.services.google_maps', array_merge(config('services.google_maps', []), $googleMaps)); - Setting::configure('system.services.twilio', array_merge(config('services.twilio', []), $twilio)); - Setting::configure('system.services.sentry', array_merge(config('sentry', []), $sentry)); + Setting::configureSystem('services.aws', array_merge(config('services.aws', []), $aws)); + Setting::configureSystem('services.ipinfo', array_merge(config('services.ipinfo', []), $ipinfo)); + Setting::configureSystem('services.google_maps', array_merge(config('services.google_maps', []), $googleMaps)); + Setting::configureSystem('services.twilio', array_merge(config('services.twilio', []), $twilio)); + Setting::configureSystem('services.sentry', array_merge(config('sentry', []), $sentry)); return response()->json(['status' => 'OK']); } @@ -470,8 +467,8 @@ public function saveNotificationChannelsConfig(AdminRequest $request) // Get credentials config array from file contents $firebase = static::_setupFcmConfigUsingFileId($firebase); - Setting::configure('system.broadcasting.apn', array_merge(config('broadcasting.connections.apn', []), $apn)); - Setting::configure('system.firebase.app', array_merge(config('firebase.projects.app', []), $firebase)); + Setting::configureSystem('broadcasting.apn', array_merge(config('broadcasting.connections.apn', []), $apn)); + Setting::configureSystem('firebase.app', array_merge(config('firebase.projects.app', []), $firebase)); return response()->json(['status' => 'OK']); } diff --git a/src/Models/Setting.php b/src/Models/Setting.php index 5960a04..d724415 100644 --- a/src/Models/Setting.php +++ b/src/Models/Setting.php @@ -8,8 +8,11 @@ use Fleetbase\Traits\HasApiModelBehavior; use Fleetbase\Traits\Searchable; use Illuminate\Database\Eloquent\Model as EloquentModel; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Str; class Setting extends EloquentModel { @@ -127,29 +130,30 @@ protected static function boot() */ public static function system($key, $defaultValue = null) { + if (!$key || !is_string($key)) { + return $defaultValue; + } + $cacheKey = 'system_settings.' . $key; // Attempt to get the value from the cache return cache()->rememberForever($cacheKey, function () use ($key, $defaultValue) { + $prefix = Str::startsWith($key, 'system.') ? '' : 'system.'; $segments = explode('.', $key); - $settingKey = 'system.' . $key; - $queryKey = 'system.' . $segments[0] . '.' . $segments[1]; + $settingKey = $prefix . $key; if (count($segments) >= 3) { - $subKey = implode('.', array_slice($segments, 2)); - $setting = static::where('key', $queryKey)->first(); + $queryKey = $prefix . $segments[0] . '.' . $segments[1]; + $subKey = implode('.', array_slice($segments, 2)); + $setting = static::where('key', $queryKey)->first(); if ($setting) { return data_get($setting->value, $subKey, $defaultValue); } + } - $setting = static::where('key', $settingKey)->first(); - - return $setting ? $setting->value : $defaultValue; - } else { - $setting = static::where('key', $settingKey)->first(); + $setting = static::where('key', $settingKey)->first(); - return $setting ? $setting->value : $defaultValue; - } + return $setting ? $setting->value : $defaultValue; }); } @@ -165,27 +169,36 @@ public static function system($key, $defaultValue = null) */ public static function configureSystem($key, $value = null): ?Setting { - return static::configure('system.' . $key, $value); + return static::configure('system.' . $key, $value, function () { + static::clearSystemCache(); + }); } /** * Updates a system setting by key and value, or creates it if it does not exist. * This function is typically used to dynamically configure system settings during runtime. * - * @param string $key the key of the setting to update or create - * @param mixed $value The value to set for the setting. If null, it updates the setting with null. + * @param string $key the key of the setting to update or create + * @param mixed $value The value to set for the setting. If null, it updates the setting with null. + * @param \Closure|null $callback a callback to do something with the setting record * * @return Setting|null the updated or created setting instance, or null if the operation fails */ - public static function configure($key, $value = null): ?Setting + public static function configure($key, $value = null, ?\Closure $callback = null): ?Setting { - return static::updateOrCreate( + $setting = static::updateOrCreate( ['key' => $key], [ 'key' => $key, 'value' => $value, ] ); + + if (is_callable($callback)) { + $callback($setting); + } + + return $setting; } /** @@ -294,7 +307,7 @@ public static function getBranding() $defaultTheme = static::where('key', 'branding.default_theme')->value('value'); // get icon file record - if (\Illuminate\Support\Str::isUuid($iconUuid)) { + if (Str::isUuid($iconUuid)) { $icon = File::where('uuid', $iconUuid)->first(); if ($icon && $icon instanceof File) { @@ -303,7 +316,7 @@ public static function getBranding() } // getlogo file record - if (\Illuminate\Support\Str::isUuid($logoUuid)) { + if (Str::isUuid($logoUuid)) { $logo = File::where('uuid', $logoUuid)->first(); if ($logo && $logo instanceof File) { @@ -332,7 +345,7 @@ public static function getBrandingLogoUrl() { $logoUuid = static::where('key', 'branding.logo_uuid')->value('value'); - if (\Illuminate\Support\Str::isUuid($logoUuid)) { + if (Str::isUuid($logoUuid)) { $logo = File::where('uuid', $logoUuid)->first(); if ($logo && $logo instanceof File) { @@ -356,7 +369,7 @@ public static function getBrandingIconUrl() { $iconUuid = static::where('key', 'branding.icon_uuid')->value('value'); - if (\Illuminate\Support\Str::isUuid($iconUuid)) { + if (Str::isUuid($iconUuid)) { $icon = File::where('uuid', $iconUuid)->first(); if ($icon && $icon instanceof File) { @@ -434,4 +447,20 @@ public static function doesntHaveConnection(): bool { return !static::hasConnection(); } + + /** + * Clears all cache entries with keys that start with "system_setting". + * + * This method connects to the Redis cache store and retrieves all keys + * that begin with the prefix "system_setting". It then iterates through each key + * and removes the corresponding cache entry using Laravel's Cache facade. + * + * Note: This function assumes that the application is using Redis as the + * cache driver. If a different cache driver is being used, this method + * may not function as expected. + */ + public static function clearSystemCache(): void + { + Utils::clearCacheByPattern('system_setting*'); + } } diff --git a/src/Providers/CoreServiceProvider.php b/src/Providers/CoreServiceProvider.php index 45e06cb..7fa4ce5 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -2,7 +2,7 @@ namespace Fleetbase\Providers; -use Fleetbase\Models\Setting; +use Fleetbase\Support\EnvironmentMapper; use Fleetbase\Support\NotificationRegistry; use Fleetbase\Support\Utils; use Illuminate\Console\Scheduling\Schedule; @@ -162,122 +162,7 @@ public function registerCustomBladeComponents() */ public function mergeConfigFromSettings() { - if (Setting::doesntHaveConnection()) { - 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'], - 'services.sentry' => ['dsn' => 'SENTRY_DSN'], - ]; - - $settings = [ - ['settingsKey' => 'filesystem.driver', 'configKey' => 'filesystems.default'], - ['settingsKey' => 'filesystem.s3', 'configKey' => 'filesystems.disks.s3'], - ['settingsKey' => 'filesystem.gcs', 'configKey' => 'filesystems.disks.gcs'], - ['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.twilio.connections.twilio'], - ['settingsKey' => 'services.ipinfo', 'configKey' => 'services.ipinfo'], - ['settingsKey' => 'services.ipinfo', 'configKey' => 'fleetbase.services.ipinfo'], - ['settingsKey' => 'services.sentry.dsn', 'configKey' => 'sentry.dsn'], - ['settingsKey' => 'broadcasting.apn', 'configKey' => 'broadcasting.connections.apn'], - ['settingsKey' => 'firebase.app', 'configKey' => 'firebase.projects.app'], - ]; - - $priorityEnvs = [ - 'AWS_ACCESS_KEY_ID' => ['services.aws.key'], - 'AWS_SECRET_ACCESS_KEY' => ['services.aws.secret', 'filesystems.disks.s3.secret', 'cache.stores.dynamodb.secret', 'queue.connections.sqs.secret', 'mail.mailers.ses.secret'], - 'AWS_DEFAULT_REGION' => ['services.aws.region', 'filesystems.disks.s3.region', 'cache.stores.dynamodb.region', 'queue.connections.sqs.region', 'mail.mailers.ses.region'], - 'AWS_BUCKET' => ['filesystems.disks.s3'], - 'FILESYSTEM_DRIVER' => ['filesystems.default'], - 'MAIL_MAILER' => ['mail.default'], - 'QUEUE_CONNECTION' => ['queue.default'], - 'SQS_PREFIX' => ['queue.connections.sqs'], - 'MAIL_FROM_ADDRESS' => ['mail.from'], - 'MAIL_HOST' => ['mail.mailers.smtp'], - ]; - - foreach ($settings as $setting) { - $settingsKey = $setting['settingsKey']; - $configKey = $setting['configKey']; - - // Check if the setting should be skipped based on priorityEnvs - $shouldSkip = false; - foreach ($priorityEnvs as $envKey => $settingKeys) { - if (env($envKey) && in_array($configKey, $settingKeys)) { - $shouldSkip = true; - break; - } - } - - if ($shouldSkip) { - continue; - } - - $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) { - // hack fix for aws set envs - $hasDefaultRegion = !empty(env('AWS_DEFAULT_REGION')); - if ($hasDefaultRegion && \Illuminate\Support\Str::startsWith($envKey, 'AWS_')) { - continue; - } - - $envValue = data_get($value, $configEnvKey); - $doesntHaveEnvSet = empty(env($envKey)); - $hasValue = !empty($envValue) && !is_array($envValue); - - // only set if env variable is not set already - if ($doesntHaveEnvSet && $hasValue) { - 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); - } - } - - // we need a mail from set - if (empty(config('mail.from.address'))) { - config()->set('mail.from.address', Utils::getDefaultMailFromAddress()); - } + EnvironmentMapper::mergeConfigFromSettings(); } /** diff --git a/src/Support/EnvironmentMapper.php b/src/Support/EnvironmentMapper.php new file mode 100644 index 0000000..af4b664 --- /dev/null +++ b/src/Support/EnvironmentMapper.php @@ -0,0 +1,205 @@ + 'services.aws.key', + 'AWS_SECRET_ACCESS_KEY' => 'services.aws.secret', + 'AWS_DEFAULT_REGION' => 'services.aws.region', + 'AWS_BUCKET' => 'filesystems.disks.s3', + 'FILESYSTEM_DRIVER' => 'filesystems.default', + 'QUEUE_CONNECTION' => 'queue.default', + 'SQS_PREFIX' => 'queue.connections.sqs', + 'MAIL_MAILER' => 'mail.default', + 'MAIL_FROM_ADDRESS' => 'mail.from', + 'MAIL_HOST' => 'mail.mailers.smtp', + 'TWILIO_SID' => 'services.twilio.sid', + 'TWILIO_TOKEN' => 'services.twilio.token', + 'TWILIO_FROM' => 'services.twilio.from', + 'GOOGLE_MAPS_API_KEY' => 'services.google_maps.api_key', + 'GOOGLE_MAPS_LOCALE' => 'services.google_maps.locale', + 'GOOGLE_CLOUD_PROJECT_ID' => 'filesystem.gcs.project_id', + 'GOOGLE_CLOUD_KEY_FILE' => 'filesystem.gcs.key_file', + 'GOOGLE_CLOUD_KEY_FILE_ID' => 'filesystem.gcs.key_file_id', + 'GOOGLE_CLOUD_STORAGE_BUCKET' => 'filesystem.gcs.bucket', + 'GOOGLE_CLOUD_STORAGE_PATH_PREFIX' => 'filesystem.gcs.path_prefix', + 'GOOGLE_CLOUD_STORAGE_API_URI' => 'filesystem.gcs.storage_api_uri', + 'SENTRY_DSN' => 'services.sentry.dsn', + 'IPINFO_API_KEY' => 'services.ipinfo.api_key', + ]; + + /** + * @var array + * + * An array of associative arrays where each entry maps a system setting key to a + * corresponding configuration key in the application. This array is used to merge + * system settings into the application's configuration. + */ + protected static array $settings = [ + ['settingsKey' => 'filesystem.driver', 'configKey' => 'filesystems.default'], + ['settingsKey' => 'filesystem.s3', 'configKey' => 'filesystems.disks.s3'], + ['settingsKey' => 'filesystem.gcs', 'configKey' => 'filesystems.disks.gcs'], + ['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.twilio.connections.twilio'], + ['settingsKey' => 'services.ipinfo', 'configKey' => 'services.ipinfo'], + ['settingsKey' => 'services.ipinfo', 'configKey' => 'fleetbase.services.ipinfo'], + ['settingsKey' => 'services.sentry.dsn', 'configKey' => 'sentry.dsn'], + ['settingsKey' => 'broadcasting.apn', 'configKey' => 'broadcasting.connections.apn'], + ['settingsKey' => 'firebase.app', 'configKey' => 'firebase.projects.app'], + ]; + + /** + * Retrieves environment variables that are not already set in the current environment + * but are defined in the system settings. This method returns an array of environment + * variables that can be set based on the system's configuration. + * + * @return array + * An associative array where the keys are the environment variable names and + * the values are the corresponding configuration paths + */ + protected static function getSettableEnvironmentVairables(): array + { + $settableEnvironmentVariables = []; + foreach (static::$environmentVariables as $variable => $configPath) { + if (Utils::isEmpty(env($variable))) { + $settableEnvironmentVariables[$variable] = $configPath; + } + } + + return $settableEnvironmentVariables; + } + + /** + * Sets environment variables for the application based on the system settings stored + * in the database. This method checks if the environment variable is not already set + * and then sets it using the corresponding value from the system settings. + * + * If the database connection is not available, the method exits early. + */ + public static function setEnvironmentVariables(): void + { + if (Setting::doesntHaveConnection()) { + return; + } + + $environmentVariables = static::getSettableEnvironmentVairables(); + foreach ($environmentVariables as $variable => $configPath) { + $value = Setting::system($configPath); + if ($value && is_string($value)) { + putenv($variable . '="' . $value . '"'); + } + } + } + + /** + * Merges system settings from the database into the application's configuration. + * This method first sets any environment variables that can be derived from the + * system settings and then merges specific settings into the configuration. + * + * It also handles special configuration logic, such as setting a default mail + * address if none is specified. + * + * If the database connection is not available, the method exits early. + */ + public static function mergeConfigFromSettings(): void + { + if (Setting::doesntHaveConnection()) { + return; + } + + static::setEnvironmentVariables(); + + foreach (static::$settings as $setting) { + $settingsKey = $setting['settingsKey']; + $configKey = $setting['configKey']; + + static::mergeConfig($settingsKey, $configKey); + } + + static::setDefaultMailFrom(); + } + + /** + * Merges a single system setting from the database into the application's configuration. + * The method fetches the setting value and updates the application's configuration at + * the specified config key. + * + * If the database connection is not available, the method exits early. + * + * @param string $settingsKey + * The key in the system settings that contains the value to be merged into the configuration + * @param string $configKey + * The key in the application's configuration that should be updated with the value from the system settings + */ + protected static function mergeConfig(string $settingsKey, string $configKey): void + { + if (Setting::doesntHaveConnection()) { + return; + } + + $value = Setting::system($settingsKey); + if ($value) { + // 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); + } + } + + /** + * Sets the default "from" email address for the application if it is not already + * specified in the configuration. The method uses a utility function to retrieve + * a default value and updates the mail configuration accordingly. + */ + protected static function setDefaultMailFrom(): void + { + if (empty(config('mail.from.address'))) { + config()->set('mail.from.address', Utils::getDefaultMailFromAddress()); + } + } +} diff --git a/src/Support/Utils.php b/src/Support/Utils.php index c22e8ea..7ba4dec 100644 --- a/src/Support/Utils.php +++ b/src/Support/Utils.php @@ -10,6 +10,7 @@ use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Storage; @@ -2457,4 +2458,38 @@ public static function slugify($string) return trim($string, '-'); } + + /** + * Clears all cache entries that match a specific pattern. + * + * This utility function connects to the Redis cache store and retrieves + * all keys that match the provided pattern, with the prefix applied by Laravel. + * It then iterates over the matched keys, removes the prefix, and deletes the + * corresponding cache entries using Laravel's Cache facade. + * + * Note: This function is specifically designed to work with Redis as the cache driver. + * If a different cache driver is used, the function may not behave as expected. + * + * @param string $pattern The pattern to match cache keys against. + * This pattern should not include the prefix, + * as the prefix is automatically applied. + */ + public static function clearCacheByPattern(string $pattern): void + { + $redis = Redis::connection(); + $prefix = Cache::getPrefix(); + $keys = $redis->keys($prefix . $pattern); + + if (is_array($keys)) { + $keys = array_map(function ($key) { + $segments = explode(':', $key); + + return $segments[1]; + }, $keys); + + foreach ($keys as $key) { + Cache::forget($key); + } + } + } } diff --git a/src/routes.php b/src/routes.php index 552e1c2..39054de 100644 --- a/src/routes.php +++ b/src/routes.php @@ -148,53 +148,45 @@ function ($router, $controller) { $router->get('export', $controller('export')); } ); - $router->fleetbaseRoutes( - 'metrics', - function ($router, $controller) { - $router->get('iam', $controller('iam'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->get('iam-dashboard', $controller('iamDashboard'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - } - )->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->fleetbaseRoutes( - 'settings', - function ($router, $controller) { - $router->get('overview', $controller('adminOverview')); - $router->get('filesystem-config', $controller('getFilesystemConfig')); - $router->post('filesystem-config', $controller('saveFilesystemConfig')); - $router->post('test-filesystem-config', $controller('testFilesystemConfig')); - $router->get('mail-config', $controller('getMailConfig')); - $router->post('mail-config', $controller('saveMailConfig')); - $router->post('test-mail-config', $controller('testMailConfig')); - $router->get('queue-config', $controller('getQueueConfig')); - $router->post('queue-config', $controller('saveQueueConfig')); - $router->post('test-queue-config', $controller('testQueueConfig')); - $router->get('services-config', $controller('getServicesConfig')); - $router->post('services-config', $controller('saveServicesConfig')); - $router->post('test-twilio-config', $controller('testTwilioConfig')); - $router->post('test-sentry-config', $controller('testSentryConfig')); - $router->post('branding', $controller('saveBrandingSettings')); - $router->put('branding', $controller('saveBrandingSettings')); - $router->post('test-socket', $controller('testSocketcluster')); - $router->get('notification-channels-config', $controller('getNotificationChannelsConfig')); - $router->post('notification-channels-config', $controller('saveNotificationChannelsConfig')); - $router->post('test-notification-channels-config', $controller('testNotificationChannelsConfig')); - } - )->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->fleetbaseRoutes( - 'schedule-monitor', - function ($router, $controller) { - $router->get('tasks', $controller('tasks'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->get('{id}/logs', $controller('logs'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - } - )->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->fleetbaseRoutes( - 'two-fa', - function ($router, $controller) { - $router->post('config', $controller('saveSystemConfig'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->get('config', $controller('getSystemConfig'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->get('enforce', $controller('shouldEnforce'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - } - )->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); + $router->fleetbaseRoutes('metrics', null, ['middleware' => [Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]], function ($router, $controller) { + $router->get('iam', $controller('iam')); + $router->get('iam-dashboard', $controller('iamDashboard')); + } + ); + $router->fleetbaseRoutes('settings', null, ['middleware' => [Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]], function ($router, $controller) { + $router->get('overview', $controller('adminOverview')); + $router->get('filesystem-config', $controller('getFilesystemConfig')); + $router->post('filesystem-config', $controller('saveFilesystemConfig')); + $router->post('test-filesystem-config', $controller('testFilesystemConfig')); + $router->get('mail-config', $controller('getMailConfig')); + $router->post('mail-config', $controller('saveMailConfig')); + $router->post('test-mail-config', $controller('testMailConfig')); + $router->get('queue-config', $controller('getQueueConfig')); + $router->post('queue-config', $controller('saveQueueConfig')); + $router->post('test-queue-config', $controller('testQueueConfig')); + $router->get('services-config', $controller('getServicesConfig')); + $router->post('services-config', $controller('saveServicesConfig')); + $router->post('test-twilio-config', $controller('testTwilioConfig')); + $router->post('test-sentry-config', $controller('testSentryConfig')); + $router->post('branding', $controller('saveBrandingSettings')); + $router->put('branding', $controller('saveBrandingSettings')); + $router->post('test-socket', $controller('testSocketcluster')); + $router->get('notification-channels-config', $controller('getNotificationChannelsConfig')); + $router->post('notification-channels-config', $controller('saveNotificationChannelsConfig')); + $router->post('test-notification-channels-config', $controller('testNotificationChannelsConfig')); + } + ); + $router->fleetbaseRoutes('schedule-monitor', null, ['middleware' => [Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]], function ($router, $controller) { + $router->get('tasks', $controller('tasks')); + $router->get('{id}/logs', $controller('logs')); + } + ); + $router->fleetbaseRoutes('two-fa', null, ['middleware' => [Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]], function ($router, $controller) { + $router->post('config', $controller('saveSystemConfig')); + $router->get('config', $controller('getSystemConfig')); + $router->get('enforce', $controller('shouldEnforce')); + } + ); $router->fleetbaseRoutes('api-events'); $router->fleetbaseRoutes('api-request-logs'); $router->fleetbaseRoutes( @@ -207,31 +199,29 @@ function ($router, $controller) { } ); $router->fleetbaseRoutes('webhook-request-logs'); - $router->fleetbaseRoutes('companies', function ($router, $controller) { - $router->get('two-fa', $controller('getTwoFactorSettings'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); + $router->fleetbaseRoutes('companies', null, ['middleware' => [Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]], function ($router, $controller) { + $router->get('two-fa', $controller('getTwoFactorSettings')); $router->post('two-fa', $controller('saveTwoFactorSettings')); - $router->match(['get', 'post'], 'export', $controller('export'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->get('{id}/users', $controller('users'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - })->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->fleetbaseRoutes( - 'users', - function ($router, $controller) { - $router->get('me', $controller('current'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->match(['get', 'post'], 'export', $controller('export'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->patch('deactivate/{id}', $controller('deactivate')); - $router->patch('activate/{id}', $controller('activate')); - $router->delete('remove-from-company/{id}', $controller('removeFromCompany')); - $router->delete('bulk-delete', $controller('bulkDelete')); - $router->post('resend-invite', $controller('resendInvitation')); - $router->post('set-password', $controller('setCurrentUserPassword')); - $router->post('validate-password', $controller('validatePassword')); - $router->post('change-password', $controller('changeUserPassword')); - $router->post('two-fa', $controller('saveTwoFactorSettings')); - $router->get('two-fa', $controller('getTwoFactorSettings'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - $router->post('locale', $controller('setUserLocale')); - $router->get('locale', $controller('getUserLocale'))->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); - } - )->middleware([Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]); + $router->match(['get', 'post'], 'export', $controller('export')); + $router->get('{id}/users', $controller('users')); + }); + $router->fleetbaseRoutes('users', null, ['middleware' => [Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]], function ($router, $controller) { + $router->get('me', $controller('current')); + $router->match(['get', 'post'], 'export', $controller('export')); + $router->patch('deactivate/{id}', $controller('deactivate')); + $router->patch('activate/{id}', $controller('activate')); + $router->delete('remove-from-company/{id}', $controller('removeFromCompany')); + $router->delete('bulk-delete', $controller('bulkDelete')); + $router->post('resend-invite', $controller('resendInvitation')); + $router->post('set-password', $controller('setCurrentUserPassword')); + $router->post('validate-password', $controller('validatePassword')); + $router->post('change-password', $controller('changeUserPassword')); + $router->post('two-fa', $controller('saveTwoFactorSettings')); + $router->get('two-fa', $controller('getTwoFactorSettings')); + $router->post('locale', $controller('setUserLocale')); + $router->get('locale', $controller('getUserLocale')); + } + ); $router->fleetbaseRoutes('user-devices'); $router->fleetbaseRoutes('groups'); $router->fleetbaseRoutes('roles');