imageHashService = $imageHashService; } public function index() { return Inertia::render('admin/duplicate-management', [ 'title' => 'Duplicate Management', ]); } public function scan(Request $request) { $threshold = $request->input('threshold', 5); $records = MemeMedia::whereNotNull('image_hash') ->whereNotNull('webp_url') ->where('image_hash', '!=', '') ->get(['id', 'name', 'image_hash', 'webp_url', 'group']); if ($records->isEmpty()) { return response()->json([ 'duplicates' => [], 'message' => 'No records with image hashes found.', ]); } $duplicates = []; $processed = []; foreach ($records as $record) { if (in_array($record->id, $processed)) { continue; } // Skip if hash is empty or invalid if (empty($record->image_hash)) { continue; } $similarRecords = []; foreach ($records as $compareRecord) { if ($record->id === $compareRecord->id || in_array($compareRecord->id, $processed)) { continue; } // Skip if either hash is empty or invalid if (empty($record->image_hash) || empty($compareRecord->image_hash)) { continue; } $distance = $this->imageHashService->calculateHammingDistance( $record->image_hash, $compareRecord->image_hash ); // Skip if distance calculation failed (returns PHP_INT_MAX) if ($distance === PHP_INT_MAX) { continue; } if ($distance <= $threshold) { $similarRecords[] = [ 'id' => $compareRecord->id, 'name' => $compareRecord->name, 'distance' => $distance, 'url' => $compareRecord->webp_url, 'group' => $compareRecord->group, ]; $processed[] = $compareRecord->id; } } if (! empty($similarRecords)) { // Sort similar records to prioritize group 2, then by ID usort($similarRecords, function ($a, $b) { if ($a['group'] == $b['group']) { return $a['id'] <=> $b['id']; } return $b['group'] <=> $a['group']; // Higher group first }); $duplicates[] = [ 'original' => [ 'id' => $record->id, 'name' => $record->name, 'url' => $record->webp_url, 'group' => $record->group, ], 'duplicates' => $similarRecords, ]; $processed[] = $record->id; } } return response()->json([ 'duplicates' => $duplicates, 'total_groups' => count($duplicates), 'total_duplicates' => array_sum(array_map(function ($group) { return count($group['duplicates']); }, $duplicates)), ]); } public function delete(Request $request) { $request->validate([ 'id' => 'required|exists:meme_medias,id', ]); $record = MemeMedia::findOrFail($request->id); // Soft delete the record $record->delete(); return response()->json([ 'success' => true, 'message' => "Deleted '{$record->name}' (Group {$record->group})", ]); } public function regenerateHash(Request $request) { $request->validate([ 'id' => 'required|exists:meme_medias,id', ]); $record = MemeMedia::findOrFail($request->id); if (! $record->webp_url) { return response()->json([ 'success' => false, 'message' => 'No WebP URL found for this record', ], 400); } $hash = $this->imageHashService->generateHashFromUrl($record->webp_url); if (! $hash) { return response()->json([ 'success' => false, 'message' => 'Failed to generate hash', ], 500); } $record->update(['image_hash' => $hash]); return response()->json([ 'success' => true, 'message' => 'Hash regenerated successfully', 'hash' => $hash, ]); } }