refactor: reorganize project structure for better maintainability
- 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)
This commit is contained in:
224
scripts/maintenance/populate_images_from_filesystem.php
Normal file
224
scripts/maintenance/populate_images_from_filesystem.php
Normal file
@@ -0,0 +1,224 @@
|
||||
<?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";
|
||||
Reference in New Issue
Block a user