Files
hub/app/Console/Commands/MigrateImagesToMinIO.php
kelly 84e81272a5 feat: Product and inventory management system with media improvements
- Complete product and inventory management system
- Media storage service with proper path conventions
- Image handling with dynamic resizing
- Database migrations for inventory tracking
- Import tools for legacy data migration
- Documentation improvements

- InventoryItem, InventoryMovement, InventoryAlert models with hashid support
- Purchase order tracking on inventory alerts
- Inventory dashboard for sellers
- Stock level monitoring and notifications

- MediaStorageService enforcing consistent path conventions
- Image controller with dynamic resizing capabilities
- Migration tools for moving images to MinIO
- Proper slug-based paths (not IDs or hashids)

- ImportProductsFromRemote command
- ImportAlohaSales, ImportThunderBudBulk commands
- ExploreRemoteDatabase for schema inspection
- Legacy data migration utilities

- Product variations table
- Remote customer mappings
- Performance indexes for stats queries
- Social media fields for brands
- Module flags for businesses

- New migrations for inventory, hashids, performance indexes
- New services: MediaStorageService
- New middleware: EnsureBusinessHasModule, EnsureUserHasCapability
- Import commands for legacy data
- Inventory models and controllers
- Updated views for marketplace and seller areas
- Documentation reorganization (archived old docs)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 18:40:54 -07:00

163 lines
4.8 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Brand;
use App\Models\Business;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class MigrateImagesToMinIO extends Command
{
protected $signature = 'media:migrate-to-minio {--dry-run : Show what would be migrated without actually doing it}';
protected $description = 'Migrate existing brand images from storage/app/public to MinIO with proper hierarchy';
protected int $migratedLogos = 0;
protected int $migratedBanners = 0;
protected int $errors = 0;
public function handle(): int
{
$dryRun = $this->option('dry-run');
if ($dryRun) {
$this->warn('🔍 DRY RUN MODE - No files will be moved or database records updated');
$this->newLine();
} else {
$this->info('🚀 Starting image migration to MinIO...');
$this->newLine();
}
// Get all brands with images
$brands = Brand::with('business')
->where(function ($query) {
$query->whereNotNull('logo_path')
->orWhereNotNull('banner_path');
})
->get();
if ($brands->isEmpty()) {
$this->info('✅ No brands with images found. Nothing to migrate.');
return 0;
}
$this->info("Found {$brands->count()} brands with images");
$this->newLine();
$progressBar = $this->output->createProgressBar($brands->count());
$progressBar->start();
foreach ($brands as $brand) {
$this->migrateBrandImages($brand, $dryRun);
$progressBar->advance();
}
$progressBar->finish();
$this->newLine(2);
// Summary
$this->info('✅ Migration Complete!');
$this->table(
['Metric', 'Count'],
[
['Logos Migrated', $this->migratedLogos],
['Banners Migrated', $this->migratedBanners],
['Errors', $this->errors],
]
);
if (! $dryRun && $this->errors === 0) {
$this->newLine();
$this->info('🎉 All images successfully migrated to MinIO!');
$this->info('📂 Check MinIO console: http://localhost:9001');
$this->info('🗑️ You can now safely delete storage/app/public/brands/');
}
return 0;
}
protected function migrateBrandImages(Brand $brand, bool $dryRun): void
{
$business = $brand->business;
// Migrate logo
if ($brand->logo_path) {
$this->migrateImage(
$brand,
$business,
$brand->logo_path,
'logo',
$dryRun
);
}
// Migrate banner
if ($brand->banner_path) {
$this->migrateImage(
$brand,
$business,
$brand->banner_path,
'banner',
$dryRun
);
}
}
protected function migrateImage(
Brand $brand,
Business $business,
string $oldPath,
string $type,
bool $dryRun
): void {
try {
// Check if file exists in old location
$oldDisk = Storage::disk('public');
if (! $oldDisk->exists($oldPath)) {
$this->newLine();
$this->warn(" ⚠️ File not found: {$oldPath} (skipping)");
$this->errors++;
return;
}
// Determine file extension
$extension = pathinfo($oldPath, PATHINFO_EXTENSION);
// Build new path using our hierarchy
$newPath = "businesses/{$business->slug}/brands/{$brand->slug}/branding/{$type}.{$extension}";
if ($dryRun) {
$this->newLine();
$this->line(' 📋 Would migrate:');
$this->line(" From: {$oldPath}");
$this->line(" To: {$newPath}");
} else {
// Get file contents
$fileContents = $oldDisk->get($oldPath);
// Upload to MinIO using our new hierarchy
$minioDisk = Storage::disk('minio');
$minioDisk->put($newPath, $fileContents);
// Update database
if ($type === 'logo') {
$brand->update(['logo_path' => $newPath]);
$this->migratedLogos++;
} else {
$brand->update(['banner_path' => $newPath]);
$this->migratedBanners++;
}
}
} catch (\Exception $e) {
$this->newLine();
$this->error(" ❌ Error migrating {$type} for {$brand->name}: ".$e->getMessage());
$this->errors++;
}
}
}