From 0908576ef20f0c39f33076df194ec7be4365289f Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 19 Aug 2026 01:09:52 +0700 Subject: [PATCH] feat: implement FixRawMaterialMediaKeys to correct S3 keys and create media records for RawMaterialPrice --- app/DataFixes/FixRawMaterialMediaKeys.php | 182 ++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 app/DataFixes/FixRawMaterialMediaKeys.php diff --git a/app/DataFixes/FixRawMaterialMediaKeys.php b/app/DataFixes/FixRawMaterialMediaKeys.php new file mode 100644 index 0000000..e85d002 --- /dev/null +++ b/app/DataFixes/FixRawMaterialMediaKeys.php @@ -0,0 +1,182 @@ +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(), + ]; + } +}