getClient(); $bucket = config('filesystems.disks.s3.bucket'); $this->info('Loading S3 file list...'); $s3Files = []; $continuationToken = null; do { $params = [ 'Bucket' => $bucket, 'Prefix' => 'raw-material/images/', ]; if ($continuationToken) { $params['ContinuationToken'] = $continuationToken; } $result = $client->listObjectsV2($params); foreach ($result['Contents'] ?? [] as $obj) { $key = $obj['Key']; if (str_contains($key, '/conversions/')) { continue; } if (preg_match('#raw-material/images/\d{4}-\d{2}-\d{2}/(\d+)/[^/]+$#', $key, $m)) { $s3Files[(int) $m[1]] = $key; } } $continuationToken = $result['NextContinuationToken'] ?? null; } while ($continuationToken); $this->info('Found ' . count($s3Files) . ' files in S3.'); // Build filename → s3_key map for matching $s3ByKey = []; foreach ($s3Files as $modelId => $s3Key) { $fileName = pathinfo($s3Key, PATHINFO_BASENAME); $s3ByKey[$modelId][$fileName] = $s3Key; } $allPrices = RawMaterialPrice::all(['id', 'variant']); $existingMediaAll = DB::table('media') ->where('model_type', 'App\\Models\\RawMaterialPrice') ->where('collection_name', 'images') ->get(['id', 'model_id', 'file_name', 'custom_properties']); $mediaByModel = []; foreach ($existingMediaAll as $m) { $props = json_decode($m->custom_properties, true); if (isset($props['module']) && $props['module'] === 'owner_verification_request') { continue; } $mediaByModel[$m->model_id][] = $m; } $fixed = 0; $fixedPath = 0; $skipped = 0; $noFile = 0; $details = []; foreach ($allPrices as $price) { if (isset($mediaByModel[$price->id])) { $allGood = true; foreach ($mediaByModel[$price->id] as $media) { $currentKey = data_get(json_decode($media->custom_properties, true), 's3_key'); if ($currentKey && str_starts_with($currentKey, 'raw-material/images/')) { continue; } // Match by filename first, fallback to model_id $correctKey = null; if (isset($s3ByKey[$price->id][$media->file_name])) { $correctKey = $s3ByKey[$price->id][$media->file_name]; } elseif (isset($s3Files[$price->id])) { $correctKey = $s3Files[$price->id]; } if ($correctKey) { DB::table('media') ->where('id', $media->id) ->update([ 'custom_properties' => DB::raw("'".addslashes(json_encode(['s3_key' => $correctKey]))."'"), 'updated_at' => now(), ]); $details[] = [ 'Price ID' => $price->id, 'Variant' => $price->variant, 'Action' => 'FIXED PATH', 'S3 Key' => $correctKey, ]; $fixedPath++; $allGood = false; } } if ($allGood) { $skipped++; } continue; } if (! isset($s3Files[$price->id])) { $noFile++; continue; } $s3Key = $s3Files[$price->id]; $fileName = pathinfo($s3Key, PATHINFO_BASENAME); DB::table('media')->insert([ 'model_type' => 'App\\Models\\RawMaterialPrice', 'model_id' => $price->id, 'uuid' => Str::uuid()->toString(), 'collection_name' => 'images', 'name' => pathinfo($s3Key, PATHINFO_FILENAME), 'file_name' => $fileName, 'mime_type' => 'image/jpeg', 'disk' => 's3', 'conversions_disk' => 's3', 'size' => 0, 'manipulations' => '[]', 'custom_properties' => json_encode(['s3_key' => $s3Key]), 'generated_conversions' => '[]', 'responsive_images' => '[]', 'order_column' => 1, 'created_at' => now(), 'updated_at' => now(), ]); $details[] = [ 'Price ID' => $price->id, 'Variant' => $price->variant, 'Action' => 'CREATED', 'S3 Key' => $s3Key, ]; $fixed++; } if (! empty($details)) { $this->command->newLine(); $this->command->table(['Price ID', 'Variant', 'Action', 'S3 Key'], $details); } $this->info("Created new media: {$fixed}"); $this->info("Fixed s3_key path: {$fixedPath}"); $this->info("Skipped (already correct): {$skipped}"); $this->info("Skipped (no file in S3): {$noFile}"); return [ 'fixed' => $fixed + $fixedPath, 'skipped' => $skipped, 'total' => $allPrices->count(), ]; } }