- Move 45 debug/test files from root to organized scripts/ directories - Secure public/ directory by removing debug files (security improvement) - Create structured scripts organization: • scripts/debug/ (20 files) - Framework debugging tools • scripts/test/ (18 files) - Test and validation scripts • scripts/maintenance/ (5 files) - Maintenance utilities • scripts/dev/ (2 files) - Development tools Security improvements: - Removed all debug/test files from public/ directory - Only production files remain: index.php, health.php Root directory cleanup: - Reduced from 47 to 2 PHP files in root - Only essential production files: console.php, worker.php This improves: ✅ Security (no debug code in public/) ✅ Organization (clear separation of concerns) ✅ Maintainability (easy to find and manage scripts) ✅ Professional structure (clean root directory)
224 lines
6.6 KiB
PHP
224 lines
6.6 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* Script to populate the database with images found in the filesystem
|
||
*
|
||
* This script scans storage/uploads/ for image files and creates database records
|
||
* to match the existing files on disk.
|
||
*/
|
||
|
||
require_once 'vendor/autoload.php';
|
||
|
||
use App\Domain\Media\Image;
|
||
use App\Framework\Core\ValueObjects\FileSize;
|
||
use App\Framework\Core\ValueObjects\Hash;
|
||
use App\Framework\Database\DatabaseManager;
|
||
use App\Framework\Filesystem\FilePath;
|
||
use App\Framework\Http\MimeType;
|
||
use App\Framework\Ulid\Ulid;
|
||
use App\Framework\Ulid\UlidGenerator;
|
||
use App\Framework\DateTime\SystemClock;
|
||
|
||
class ImageMigrationScript
|
||
{
|
||
private PDO $db;
|
||
private SystemClock $clock;
|
||
private string $uploadsPath;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->clock = new SystemClock();
|
||
$this->uploadsPath = __DIR__ . '/storage/uploads';
|
||
|
||
// Initialize database
|
||
$this->initializeDatabase();
|
||
}
|
||
|
||
private function initializeDatabase(): void
|
||
{
|
||
// Simple SQLite connection for this script
|
||
$pdo = new PDO('sqlite:' . __DIR__ . '/database.sqlite');
|
||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||
$this->db = $pdo;
|
||
}
|
||
|
||
public function run(): void
|
||
{
|
||
echo "🔍 Scanning for images in: {$this->uploadsPath}\n";
|
||
|
||
if (!is_dir($this->uploadsPath)) {
|
||
echo "❌ Uploads directory not found: {$this->uploadsPath}\n";
|
||
return;
|
||
}
|
||
|
||
$imageFiles = $this->findImageFiles();
|
||
echo "📁 Found " . count($imageFiles) . " image files\n";
|
||
|
||
if (empty($imageFiles)) {
|
||
echo "ℹ️ No images to migrate\n";
|
||
return;
|
||
}
|
||
|
||
$this->migrateImages($imageFiles);
|
||
echo "✅ Migration completed!\n";
|
||
}
|
||
|
||
private function findImageFiles(): array
|
||
{
|
||
$iterator = new RecursiveIteratorIterator(
|
||
new RecursiveDirectoryIterator($this->uploadsPath)
|
||
);
|
||
|
||
$imageFiles = [];
|
||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||
|
||
foreach ($iterator as $file) {
|
||
if (!$file->isFile()) {
|
||
continue;
|
||
}
|
||
|
||
$extension = strtolower($file->getExtension());
|
||
if (!in_array($extension, $allowedExtensions)) {
|
||
continue;
|
||
}
|
||
|
||
$imageFiles[] = [
|
||
'path' => $file->getPathname(),
|
||
'filename' => $file->getFilename(),
|
||
'extension' => $extension,
|
||
'size' => $file->getSize(),
|
||
'mtime' => $file->getMTime()
|
||
];
|
||
}
|
||
|
||
return $imageFiles;
|
||
}
|
||
|
||
private function migrateImages(array $imageFiles): void
|
||
{
|
||
// Check current database schema
|
||
$this->checkDatabaseSchema();
|
||
|
||
$migrated = 0;
|
||
$errors = 0;
|
||
|
||
foreach ($imageFiles as $fileInfo) {
|
||
try {
|
||
$this->migrateImageFile($fileInfo);
|
||
$migrated++;
|
||
echo "✓ Migrated: {$fileInfo['filename']}\n";
|
||
} catch (Exception $e) {
|
||
$errors++;
|
||
echo "❌ Error migrating {$fileInfo['filename']}: " . $e->getMessage() . "\n";
|
||
}
|
||
}
|
||
|
||
echo "\n📊 Summary:\n";
|
||
echo " Migrated: $migrated\n";
|
||
echo " Errors: $errors\n";
|
||
}
|
||
|
||
private function checkDatabaseSchema(): void
|
||
{
|
||
// Check what columns exist in the images table
|
||
$stmt = $this->db->query("PRAGMA table_info(images)");
|
||
$columns = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
echo "📋 Database schema (images table):\n";
|
||
foreach ($columns as $column) {
|
||
echo " - {$column['name']} ({$column['type']})\n";
|
||
}
|
||
echo "\n";
|
||
}
|
||
|
||
private function migrateImageFile(array $fileInfo): void
|
||
{
|
||
$fullPath = $fileInfo['path'];
|
||
|
||
// Extract image dimensions if possible
|
||
$imageInfo = @getimagesize($fullPath);
|
||
$width = $imageInfo[0] ?? 0;
|
||
$height = $imageInfo[1] ?? 0;
|
||
|
||
// Generate ULID
|
||
$ulidGenerator = new UlidGenerator();
|
||
$ulidString = $ulidGenerator->generate($this->clock);
|
||
|
||
// Calculate hash
|
||
$hashValue = hash_file('sha256', $fullPath);
|
||
|
||
// Determine MIME type
|
||
$mimeTypeString = match (strtolower($fileInfo['extension'])) {
|
||
'jpg', 'jpeg' => 'image/jpeg',
|
||
'png' => 'image/png',
|
||
'gif' => 'image/gif',
|
||
'webp' => 'image/webp',
|
||
default => 'image/jpeg'
|
||
};
|
||
|
||
// Extract original filename from the complex filename structure
|
||
$originalFilename = $this->extractOriginalFilename($fileInfo['filename']);
|
||
|
||
// Get relative path from storage root
|
||
$relativePath = str_replace($this->uploadsPath . '/', '', $fullPath);
|
||
$pathOnly = dirname($relativePath);
|
||
|
||
// Insert into database using the correct table structure
|
||
$sql = "INSERT INTO images (
|
||
ulid, filename, original_filename, mime_type, file_size,
|
||
width, height, hash, path, created_at, updated_at
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||
|
||
$stmt = $this->db->prepare($sql);
|
||
$now = date('Y-m-d H:i:s');
|
||
|
||
$stmt->execute([
|
||
$ulidString,
|
||
$fileInfo['filename'],
|
||
$originalFilename,
|
||
$mimeTypeString,
|
||
$fileInfo['size'],
|
||
$width,
|
||
$height,
|
||
$hashValue,
|
||
$pathOnly,
|
||
$now,
|
||
$now
|
||
]);
|
||
}
|
||
|
||
private function extractOriginalFilename(string $filename): string
|
||
{
|
||
// Pattern for files like: BFWCAKKEHTKF5SYR_6626fc6b...cd1_original.png
|
||
if (preg_match('/^[A-Z0-9]{16}_[a-f0-9]{64}_original\.(.+)$/', $filename, $matches)) {
|
||
// This is an original file, try to find the pattern in other files
|
||
$basePattern = substr($filename, 0, strpos($filename, '_original.'));
|
||
// For now, just return a cleaned version
|
||
return "original." . $matches[1];
|
||
}
|
||
|
||
// Pattern for simple files like: 00MF9VW9R36NJN3VCFSTS2CK6R.jpg
|
||
if (preg_match('/^[A-Z0-9]{26}\.(.+)$/', $filename, $matches)) {
|
||
return "image." . $matches[1];
|
||
}
|
||
|
||
// Fallback: return as-is
|
||
return $filename;
|
||
}
|
||
}
|
||
|
||
// Run the migration
|
||
echo "🚀 Starting image migration from filesystem to database...\n\n";
|
||
|
||
try {
|
||
$migration = new ImageMigrationScript();
|
||
$migration->run();
|
||
} catch (Exception $e) {
|
||
echo "💥 Migration failed: " . $e->getMessage() . "\n";
|
||
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
|
||
exit(1);
|
||
}
|
||
|
||
echo "\n🎉 Migration script completed!\n"; |