2026-01-26 23:56:46 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
2026-01-27 16:32:55 +00:00
|
|
|
|
namespace Core\Mod\Uptelligence\Models;
|
2026-01-26 23:56:46 +00:00
|
|
|
|
|
|
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Diff Cache - stores file changes for version releases.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Auto-categorises files for filtering and prioritisation.
|
|
|
|
|
|
*/
|
|
|
|
|
|
class DiffCache extends Model
|
|
|
|
|
|
{
|
|
|
|
|
|
use HasFactory;
|
|
|
|
|
|
|
2026-01-29 13:29:26 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* The table associated with the model.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Uses the uptelligence_ prefix for consistency with other module tables.
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected $table = 'uptelligence_diff_cache';
|
2026-01-26 23:56:46 +00:00
|
|
|
|
|
|
|
|
|
|
// Change types
|
|
|
|
|
|
public const CHANGE_ADDED = 'added';
|
|
|
|
|
|
|
|
|
|
|
|
public const CHANGE_MODIFIED = 'modified';
|
|
|
|
|
|
|
|
|
|
|
|
public const CHANGE_REMOVED = 'removed';
|
|
|
|
|
|
|
|
|
|
|
|
// Categories (auto-detected)
|
|
|
|
|
|
public const CATEGORY_CONTROLLER = 'controller';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_MODEL = 'model';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_VIEW = 'view';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_MIGRATION = 'migration';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_CONFIG = 'config';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_ROUTE = 'route';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_LANGUAGE = 'language';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_ASSET = 'asset';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_PLUGIN = 'plugin';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_BLOCK = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_SECURITY = 'security';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_API = 'api';
|
|
|
|
|
|
|
|
|
|
|
|
public const CATEGORY_OTHER = 'other';
|
|
|
|
|
|
|
|
|
|
|
|
protected $fillable = [
|
|
|
|
|
|
'version_release_id',
|
|
|
|
|
|
'file_path',
|
|
|
|
|
|
'change_type',
|
fix(P2-058): complete migration column alignment with models
- VersionRelease: add storage fields, file counters, timestamps
- UpstreamTodo: add branch_name, assigned_to, started_at
- DiffCache: add new_content, lines_added/removed, metadata casts
- AnalysisLog: use version_release_id and error_message
- Asset: add slug, name, licence fields, install details
- AssetVersion: add changelog, breaking_changes, paths
- Webhook tables: fix foreign key to uptelligence_vendors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 18:05:38 +00:00
|
|
|
|
'category',
|
2026-01-26 23:56:46 +00:00
|
|
|
|
'diff_content',
|
|
|
|
|
|
'new_content',
|
fix(P2-058): complete migration column alignment with models
- VersionRelease: add storage fields, file counters, timestamps
- UpstreamTodo: add branch_name, assigned_to, started_at
- DiffCache: add new_content, lines_added/removed, metadata casts
- AnalysisLog: use version_release_id and error_message
- Asset: add slug, name, licence fields, install details
- AssetVersion: add changelog, breaking_changes, paths
- Webhook tables: fix foreign key to uptelligence_vendors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 18:05:38 +00:00
|
|
|
|
'lines_added',
|
|
|
|
|
|
'lines_removed',
|
|
|
|
|
|
'metadata',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
protected $casts = [
|
|
|
|
|
|
'lines_added' => 'integer',
|
|
|
|
|
|
'lines_removed' => 'integer',
|
|
|
|
|
|
'metadata' => 'array',
|
2026-01-26 23:56:46 +00:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// Relationships
|
|
|
|
|
|
public function versionRelease(): BelongsTo
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->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 => '📄',
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|