diff --git a/app/Console/Commands/CleanupNames.php b/app/Console/Commands/CleanupNames.php new file mode 100644 index 000000000..3ff0ff848 --- /dev/null +++ b/app/Console/Commands/CleanupNames.php @@ -0,0 +1,245 @@ + Project::class, + 'Environment' => Environment::class, + 'Application' => Application::class, + 'Service' => Service::class, + 'Server' => Server::class, + 'Team' => Team::class, + 'StandalonePostgresql' => StandalonePostgresql::class, + 'StandaloneMysql' => StandaloneMysql::class, + 'StandaloneRedis' => StandaloneRedis::class, + 'StandaloneMongodb' => StandaloneMongodb::class, + 'StandaloneMariadb' => StandaloneMariadb::class, + 'StandaloneKeydb' => StandaloneKeydb::class, + 'StandaloneDragonfly' => StandaloneDragonfly::class, + 'StandaloneClickhouse' => StandaloneClickhouse::class, + 'S3Storage' => S3Storage::class, + 'Tag' => Tag::class, + 'PrivateKey' => PrivateKey::class, + 'ScheduledTask' => ScheduledTask::class, + ]; + + protected array $changes = []; + + protected int $totalProcessed = 0; + + protected int $totalCleaned = 0; + + public function handle(): int + { + $this->info('๐Ÿ” Scanning for invalid characters in name fields...'); + + if ($this->option('backup') && ! $this->option('dry-run')) { + $this->createBackup(); + } + + $modelFilter = $this->option('model'); + $modelsToProcess = $modelFilter + ? [$modelFilter => $this->modelsToClean[$modelFilter] ?? null] + : $this->modelsToClean; + + if ($modelFilter && ! isset($this->modelsToClean[$modelFilter])) { + $this->error("โŒ Unknown model: {$modelFilter}"); + $this->info('Available models: '.implode(', ', array_keys($this->modelsToClean))); + + return self::FAILURE; + } + + foreach ($modelsToProcess as $modelName => $modelClass) { + if (! $modelClass) { + continue; + } + $this->processModel($modelName, $modelClass); + } + + $this->displaySummary(); + + if (! $this->option('dry-run') && $this->totalCleaned > 0) { + $this->logChanges(); + } + + return self::SUCCESS; + } + + protected function processModel(string $modelName, string $modelClass): void + { + $this->info("\n๐Ÿ“‹ Processing {$modelName}..."); + + try { + $records = $modelClass::all(['id', 'name']); + $cleaned = 0; + + foreach ($records as $record) { + $this->totalProcessed++; + + $originalName = $record->name; + $sanitizedName = $this->sanitizeName($originalName); + + if ($sanitizedName !== $originalName) { + $this->changes[] = [ + 'model' => $modelName, + 'id' => $record->id, + 'original' => $originalName, + 'sanitized' => $sanitizedName, + 'timestamp' => now(), + ]; + + if (! $this->option('dry-run')) { + // Update without triggering events/mutators to avoid conflicts + $modelClass::where('id', $record->id)->update(['name' => $sanitizedName]); + } + + $cleaned++; + $this->totalCleaned++; + + $this->warn(" ๐Ÿงน {$modelName} #{$record->id}:"); + $this->line(' From: '.$this->truncate($originalName, 80)); + $this->line(' To: '.$this->truncate($sanitizedName, 80)); + } + } + + if ($cleaned > 0) { + $action = $this->option('dry-run') ? 'would be sanitized' : 'sanitized'; + $this->info(" โœ… {$cleaned}/{$records->count()} records {$action}"); + } else { + $this->info(' โœจ No invalid characters found'); + } + + } catch (\Exception $e) { + $this->error(" โŒ Error processing {$modelName}: ".$e->getMessage()); + } + } + + protected function sanitizeName(string $name): string + { + // Remove all characters that don't match the allowed pattern + $sanitized = preg_replace('/[^a-zA-Z0-9\s\-_.]+/', '', $name); + + // Clean up excessive whitespace but preserve other allowed characters + $sanitized = preg_replace('/\s+/', ' ', $sanitized); + $sanitized = trim($sanitized); + + // If result is empty, provide a default name + if (empty($sanitized)) { + $sanitized = 'sanitized-item'; + } + + return $sanitized; + } + + protected function displaySummary(): void + { + $this->info("\n".str_repeat('=', 60)); + $this->info('๐Ÿ“Š CLEANUP SUMMARY'); + $this->info(str_repeat('=', 60)); + + $this->line("Records processed: {$this->totalProcessed}"); + $this->line("Records with invalid characters: {$this->totalCleaned}"); + + if ($this->option('dry-run')) { + $this->warn("\n๐Ÿ” DRY RUN - No changes were made to the database"); + $this->info('Run without --dry-run to apply these changes'); + } else { + if ($this->totalCleaned > 0) { + $this->info("\nโœ… Database successfully sanitized!"); + $this->info('Changes logged to storage/logs/name-cleanup.log'); + } else { + $this->info("\nโœจ No cleanup needed - all names are valid!"); + } + } + } + + protected function logChanges(): void + { + $logFile = storage_path('logs/name-cleanup.log'); + $logData = [ + 'timestamp' => now()->toISOString(), + 'total_processed' => $this->totalProcessed, + 'total_cleaned' => $this->totalCleaned, + 'changes' => $this->changes, + ]; + + file_put_contents($logFile, json_encode($logData, JSON_PRETTY_PRINT)."\n", FILE_APPEND); + + Log::info('Name Sanitization completed', [ + 'total_processed' => $this->totalProcessed, + 'total_sanitized' => $this->totalCleaned, + 'changes_count' => count($this->changes), + ]); + } + + protected function createBackup(): void + { + $this->info('๐Ÿ’พ Creating database backup...'); + + try { + $backupFile = storage_path('backups/name-cleanup-backup-'.now()->format('Y-m-d-H-i-s').'.sql'); + + // Ensure backup directory exists + if (! file_exists(dirname($backupFile))) { + mkdir(dirname($backupFile), 0755, true); + } + + $dbConfig = config('database.connections.'.config('database.default')); + $command = sprintf( + 'pg_dump -h %s -p %s -U %s -d %s > %s', + $dbConfig['host'], + $dbConfig['port'], + $dbConfig['username'], + $dbConfig['database'], + $backupFile + ); + + exec($command, $output, $returnCode); + + if ($returnCode === 0) { + $this->info("โœ… Backup created: {$backupFile}"); + } else { + $this->warn('โš ๏ธ Backup creation may have failed. Proceeding anyway...'); + } + } catch (\Exception $e) { + $this->warn('โš ๏ธ Could not create backup: '.$e->getMessage()); + $this->warn('Proceeding without backup...'); + } + } + + protected function truncate(string $text, int $length): string + { + return strlen($text) > $length ? substr($text, 0, $length).'...' : $text; + } +} diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 3a3e7af1c..3d61a0935 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -53,6 +53,11 @@ class Init extends Command $this->call('cleanup:redis'); + try { + $this->call('cleanup:names'); + } catch (\Throwable $e) { + echo "Error in cleanup:names command: {$e->getMessage()}\n"; + } $this->call('cleanup:stucked-resources'); try { diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 988eb32f6..05ceefed4 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\Project; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; use OpenApi\Attributes as OA; class ProjectController extends Controller @@ -227,9 +228,14 @@ class ProjectController extends Controller if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $validator = customApiValidator($request->all(), [ - 'name' => 'string|max:255|required', + $validator = Validator::make($request->all(), [ + 'name' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'], 'description' => 'string|nullable', + ], [ + 'name.regex' => 'The project name may only contain letters, numbers, spaces, dashes, underscores, and dots.', + 'name.required' => 'The project name is required.', + 'name.min' => 'The project name must be at least 3 characters.', + 'name.max' => 'The project name may not be greater than 255 characters.', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -337,9 +343,13 @@ class ProjectController extends Controller if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $validator = customApiValidator($request->all(), [ - 'name' => 'string|max:255|nullable', + $validator = Validator::make($request->all(), [ + 'name' => ['nullable', 'string', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'], 'description' => 'string|nullable', + ], [ + 'name.regex' => 'The project name may only contain letters, numbers, spaces, dashes, underscores, and dots.', + 'name.min' => 'The project name must be at least 3 characters.', + 'name.max' => 'The project name may not be greater than 255 characters.', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -579,8 +589,13 @@ class ProjectController extends Controller if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; } - $validator = customApiValidator($request->all(), [ - 'name' => 'string|max:255|required', + $validator = Validator::make($request->all(), [ + 'name' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'], + ], [ + 'name.regex' => 'The environment name may only contain letters, numbers, spaces, dashes, underscores, and dots.', + 'name.required' => 'The environment name is required.', + 'name.min' => 'The environment name must be at least 3 characters.', + 'name.max' => 'The environment name may not be greater than 255 characters.', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); diff --git a/app/Livewire/Project/AddEmpty.php b/app/Livewire/Project/AddEmpty.php index 07873c059..36b1edd15 100644 --- a/app/Livewire/Project/AddEmpty.php +++ b/app/Livewire/Project/AddEmpty.php @@ -9,12 +9,18 @@ use Visus\Cuid2\Cuid2; class AddEmpty extends Component { - #[Validate(['required', 'string', 'min:3'])] + #[Validate(['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'])] public string $name; #[Validate(['nullable', 'string'])] public string $description = ''; + protected $messages = [ + 'name.regex' => 'The name may only contain letters, numbers, spaces, dashes, underscores, and dots.', + 'name.min' => 'The name must be at least 3 characters.', + 'name.max' => 'The name may not be greater than 255 characters.', + ]; + public function submit() { try { diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index 57f4a0c68..e69e55df6 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -45,7 +45,10 @@ class CloneMe extends Component protected $messages = [ 'selectedServer' => 'Please select a server.', 'selectedDestination' => 'Please select a server & destination.', - 'newName' => 'Please enter a name for the new project or environment.', + 'newName.required' => 'Please enter a name for the new project or environment.', + 'newName.regex' => 'The name may only contain letters, numbers, spaces, dashes, underscores, and dots.', + 'newName.min' => 'The name must be at least 3 characters.', + 'newName.max' => 'The name may not be greater than 255 characters.', ]; public function mount($project_uuid) @@ -90,7 +93,7 @@ class CloneMe extends Component try { $this->validate([ 'selectedDestination' => 'required', - 'newName' => 'required', + 'newName' => ['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'], ]); if ($type === 'project') { $foundProject = Project::where('name', $this->newName)->first(); diff --git a/app/Livewire/Project/Edit.php b/app/Livewire/Project/Edit.php index 463febb10..800dc31e1 100644 --- a/app/Livewire/Project/Edit.php +++ b/app/Livewire/Project/Edit.php @@ -10,12 +10,18 @@ class Edit extends Component { public Project $project; - #[Validate(['required', 'string', 'min:3', 'max:255'])] + #[Validate(['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'])] public string $name; #[Validate(['nullable', 'string', 'max:255'])] public ?string $description = null; + protected $messages = [ + 'name.regex' => 'The name may only contain letters, numbers, spaces, dashes, underscores, and dots.', + 'name.min' => 'The name must be at least 3 characters.', + 'name.max' => 'The name may not be greater than 255 characters.', + ]; + public function mount(string $project_uuid) { try { diff --git a/app/Livewire/Project/EnvironmentEdit.php b/app/Livewire/Project/EnvironmentEdit.php index e98b088ec..021f499ab 100644 --- a/app/Livewire/Project/EnvironmentEdit.php +++ b/app/Livewire/Project/EnvironmentEdit.php @@ -17,12 +17,18 @@ class EnvironmentEdit extends Component #[Locked] public $environment; - #[Validate(['required', 'string', 'min:3', 'max:255'])] + #[Validate(['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'])] public string $name; #[Validate(['nullable', 'string', 'max:255'])] public ?string $description = null; + protected $messages = [ + 'name.regex' => 'The environment name may only contain letters, numbers, spaces, dashes, underscores, and dots.', + 'name.min' => 'The environment name must be at least 3 characters.', + 'name.max' => 'The environment name may not be greater than 255 characters.', + ]; + public function mount(string $project_uuid, string $environment_uuid) { try { diff --git a/app/Livewire/Project/Show.php b/app/Livewire/Project/Show.php index 886a20218..e24558530 100644 --- a/app/Livewire/Project/Show.php +++ b/app/Livewire/Project/Show.php @@ -12,12 +12,18 @@ class Show extends Component { public Project $project; - #[Validate(['required', 'string', 'min:3'])] + #[Validate(['required', 'string', 'min:3', 'max:255', 'regex:/^[a-zA-Z0-9\s\-_.]+$/'])] public string $name; #[Validate(['nullable', 'string'])] public ?string $description = null; + protected $messages = [ + 'name.regex' => 'The environment name may only contain letters, numbers, spaces, dashes, underscores, and dots.', + 'name.min' => 'The environment name must be at least 3 characters.', + 'name.max' => 'The environment name may not be greater than 255 characters.', + ]; + public function mount(string $project_uuid) { try { diff --git a/app/Models/Application.php b/app/Models/Application.php index f74ed89d1..cbbe8d83c 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Enums\ApplicationDeploymentStatus; use App\Services\ConfigurationGenerator; use App\Traits\HasConfiguration; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -109,7 +110,7 @@ use Visus\Cuid2\Cuid2; class Application extends BaseModel { - use HasConfiguration, HasFactory, SoftDeletes; + use HasConfiguration, HasFactory, HasSafeNameAttribute, SoftDeletes; private static $parserVersion = '5'; diff --git a/app/Models/Environment.php b/app/Models/Environment.php index b8f1090d8..659ec74e4 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use OpenApi\Attributes as OA; @@ -19,6 +20,8 @@ use OpenApi\Attributes as OA; )] class Environment extends BaseModel { + use HasSafeNameAttribute; + protected $guarded = []; protected static function booted() diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php index dbed7b439..128fac7af 100644 --- a/app/Models/PrivateKey.php +++ b/app/Models/PrivateKey.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use DanHarrin\LivewireRateLimiting\WithRateLimiting; use Illuminate\Support\Facades\Storage; use Illuminate\Validation\ValidationException; @@ -27,7 +28,7 @@ use phpseclib3\Crypt\PublicKeyLoader; )] class PrivateKey extends BaseModel { - use WithRateLimiting; + use HasSafeNameAttribute, WithRateLimiting; protected $fillable = [ 'name', diff --git a/app/Models/Project.php b/app/Models/Project.php index 2e4d45859..a4e92fd51 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use OpenApi\Attributes as OA; use Visus\Cuid2\Cuid2; @@ -23,6 +24,8 @@ use Visus\Cuid2\Cuid2; )] class Project extends BaseModel { + use HasSafeNameAttribute; + protected $guarded = []; public static function ownedByCurrentTeam() diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index e9d674650..86676f3d1 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -2,13 +2,14 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Storage; class S3Storage extends BaseModel { - use HasFactory; + use HasFactory, HasSafeNameAttribute; protected $guarded = []; diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php index 264a04d1f..10b9c184e 100644 --- a/app/Models/ScheduledTask.php +++ b/app/Models/ScheduledTask.php @@ -2,11 +2,14 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; class ScheduledTask extends BaseModel { + use HasSafeNameAttribute; + protected $guarded = []; public function service() diff --git a/app/Models/Server.php b/app/Models/Server.php index 41ecdafb8..553defb0c 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -13,6 +13,7 @@ use App\Jobs\RegenerateSslCertJob; use App\Notifications\Server\Reachable; use App\Notifications\Server\Unreachable; use App\Services\ConfigurationRepository; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -164,6 +165,8 @@ class Server extends BaseModel protected $guarded = []; + use HasSafeNameAttribute; + public function type() { return 'server'; diff --git a/app/Models/Service.php b/app/Models/Service.php index d79a4ea11..033c9ee42 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\ProcessStatus; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -40,7 +41,7 @@ use Visus\Cuid2\Cuid2; )] class Service extends BaseModel { - use HasFactory, SoftDeletes; + use HasFactory, HasSafeNameAttribute, SoftDeletes; private static $parserVersion = '5'; diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index fcd81cdc9..5e1b200eb 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -2,13 +2,14 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneClickhouse extends BaseModel { - use HasFactory, SoftDeletes; + use HasFactory, HasSafeNameAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index 9db6a2d29..141c0cf12 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -2,8 +2,12 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; + class StandaloneDocker extends BaseModel { + use HasSafeNameAttribute; + protected $guarded = []; protected static function boot() diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index fdf69b834..aa861ae25 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -2,13 +2,14 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneDragonfly extends BaseModel { - use HasFactory, SoftDeletes; + use HasFactory, HasSafeNameAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index d52023920..6b5700ea6 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -2,13 +2,14 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneKeydb extends BaseModel { - use HasFactory, SoftDeletes; + use HasFactory, HasSafeNameAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 5a8869b41..c6ad7dd51 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\MorphTo; @@ -9,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMariadb extends BaseModel { - use HasFactory, SoftDeletes; + use HasFactory, HasSafeNameAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index 88833eebe..95bdad5d1 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -2,13 +2,14 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMongodb extends BaseModel { - use HasFactory, SoftDeletes; + use HasFactory, HasSafeNameAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index dedc35f91..7138ef5bb 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -2,13 +2,14 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMysql extends BaseModel { - use HasFactory, SoftDeletes; + use HasFactory, HasSafeNameAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 6f1d03cc6..870a6ade8 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -2,13 +2,14 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandalonePostgresql extends BaseModel { - use HasFactory, SoftDeletes; + use HasFactory, HasSafeNameAttribute, SoftDeletes; protected $guarded = []; @@ -322,7 +323,7 @@ class StandalonePostgresql extends BaseModel $parsedCollection = collect($metrics)->map(function ($metric) { return [ (int) $metric['time'], - (float) ($metric['percent'] ?? 0.0) + (float) ($metric['percent'] ?? 0.0), ]; }); diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 7f6f2ad72..35de6602d 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -2,13 +2,14 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneRedis extends BaseModel { - use HasFactory, SoftDeletes; + use HasFactory, HasSafeNameAttribute, SoftDeletes; protected $guarded = []; diff --git a/app/Models/Tag.php b/app/Models/Tag.php index a64c994a3..8b8793673 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -2,10 +2,13 @@ namespace App\Models; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; class Tag extends BaseModel { + use HasSafeNameAttribute; + protected $guarded = []; public function name(): Attribute diff --git a/app/Models/Team.php b/app/Models/Team.php index 42b88f9e7..d5358df74 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -8,6 +8,7 @@ use App\Notifications\Channels\SendsEmail; use App\Notifications\Channels\SendsPushover; use App\Notifications\Channels\SendsSlack; use App\Traits\HasNotificationSettings; +use App\Traits\HasSafeNameAttribute; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; @@ -36,7 +37,7 @@ use OpenApi\Attributes as OA; class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack { - use HasNotificationSettings, Notifiable; + use HasNotificationSettings, HasSafeNameAttribute, Notifiable; protected $guarded = []; diff --git a/app/Traits/HasSafeNameAttribute.php b/app/Traits/HasSafeNameAttribute.php new file mode 100644 index 000000000..e6c089f70 --- /dev/null +++ b/app/Traits/HasSafeNameAttribute.php @@ -0,0 +1,14 @@ +attributes['name'] = strip_tags($value); + } +}