- 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>
163 lines
4.8 KiB
PHP
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++;
|
|
}
|
|
}
|
|
}
|