belongsTo(VersionRelease::class); } // Scopes public function scopeAdded($query) { return $query->where('change_type', self::CHANGE_ADDED); } public function scopeModified($query) { return $query->where('change_type', self::CHANGE_MODIFIED); } public function scopeRemoved($query) { return $query->where('change_type', self::CHANGE_REMOVED); } public function scopeByCategory($query, string $category) { return $query->where('category', $category); } public function scopeSecurityRelated($query) { return $query->where('category', self::CATEGORY_SECURITY); } // Helpers public static function detectCategory(string $filePath): string { $path = strtolower($filePath); // Security-related files if (str_contains($path, 'security') || str_contains($path, 'auth') || str_contains($path, 'password') || str_contains($path, 'permission') || str_contains($path, 'middleware')) { return self::CATEGORY_SECURITY; } // Controllers if (str_contains($path, '/controllers/') || str_ends_with($path, 'controller.php')) { return self::CATEGORY_CONTROLLER; } // Models if (str_contains($path, '/models/') || str_ends_with($path, 'model.php')) { return self::CATEGORY_MODEL; } // Views/Templates if (str_contains($path, '/views/') || str_contains($path, '/themes/') || str_ends_with($path, '.blade.php')) { return self::CATEGORY_VIEW; } // Migrations if (str_contains($path, '/migrations/') || str_contains($path, '/database/')) { return self::CATEGORY_MIGRATION; } // Config if (str_contains($path, '/config/') || str_ends_with($path, 'config.php')) { return self::CATEGORY_CONFIG; } // Routes if (str_contains($path, '/routes/') || str_ends_with($path, 'routes.php')) { return self::CATEGORY_ROUTE; } // Languages if (str_contains($path, '/languages/') || str_contains($path, '/lang/')) { return self::CATEGORY_LANGUAGE; } // Assets if (preg_match('/\.(css|js|scss|less|png|jpg|gif|svg|woff|ttf)$/', $path)) { return self::CATEGORY_ASSET; } // Plugins if (str_contains($path, '/plugins/')) { return self::CATEGORY_PLUGIN; } // Blocks (BioLinks specific) if (str_contains($path, '/blocks/') || str_contains($path, 'biolink')) { return self::CATEGORY_BLOCK; } // API if (str_contains($path, '/api/') || str_contains($path, 'api.php')) { return self::CATEGORY_API; } return self::CATEGORY_OTHER; } public function getFileName(): string { return basename($this->file_path); } public function getDirectory(): string { return dirname($this->file_path); } public function getExtension(): string { return pathinfo($this->file_path, PATHINFO_EXTENSION); } public function getDiffLineCount(): int { if (! $this->diff_content) { return 0; } return substr_count($this->diff_content, "\n") + 1; } public function getAddedLines(): int { if (! $this->diff_content) { return 0; } return preg_match_all('/^\+[^+]/m', $this->diff_content); } public function getRemovedLines(): int { if (! $this->diff_content) { return 0; } return preg_match_all('/^-[^-]/m', $this->diff_content); } public function getChangeTypeIcon(): string { return match ($this->change_type) { self::CHANGE_ADDED => '➕', self::CHANGE_MODIFIED => '✏️', self::CHANGE_REMOVED => '➖', default => '📄', }; } public function getChangeTypeBadgeClass(): string { return match ($this->change_type) { self::CHANGE_ADDED => 'bg-green-100 text-green-800', self::CHANGE_MODIFIED => 'bg-blue-100 text-blue-800', self::CHANGE_REMOVED => 'bg-red-100 text-red-800', default => 'bg-gray-100 text-gray-800', }; } public function getCategoryIcon(): string { return match ($this->category) { self::CATEGORY_CONTROLLER => '🎮', self::CATEGORY_MODEL => '📊', self::CATEGORY_VIEW => '👁️', self::CATEGORY_MIGRATION => '🗄️', self::CATEGORY_CONFIG => '⚙️', self::CATEGORY_ROUTE => '🛤️', self::CATEGORY_LANGUAGE => '🌐', self::CATEGORY_ASSET => '🎨', self::CATEGORY_PLUGIN => '🔌', self::CATEGORY_BLOCK => '🧱', self::CATEGORY_SECURITY => '🔒', self::CATEGORY_API => '🔌', default => '📄', }; } }