rollback(); return; } $this->seedTags(); $this->seedCalls(); $this->command->info('Re-indexing affected users in Elasticsearch (synchronously)...'); $this->reindexAffectedUsers(); $this->command->info('Done. Run with --rollback to remove test data.'); } private function seedTags(): void { $users = User::doesntHave('tags') ->inRandomOrder() ->limit(self::USERS_TO_SEED) ->get(); // Only use tag_ids that have a context with a non-null category_id $tagIds = DB::table('taggable_contexts') ->whereNotNull('category_id') ->inRandomOrder()->limit(500)->pluck('id')->toArray(); if (empty($tagIds)) { $this->command->warn('No tags found, skipping tag seeding.'); return; } $rows = []; $now = now()->toDateTimeString(); foreach ($users as $user) { $selected = collect($tagIds)->shuffle()->take(self::TAGS_PER_USER); foreach ($selected as $tagId) { $rows[] = [ 'tag_id' => $tagId, 'taggable_id' => $user->id, 'taggable_type' => 'App\Models\User', 'created_at' => $now, 'updated_at' => $now, ]; } } // Insert in chunks, ignore duplicates foreach (array_chunk($rows, 500) as $chunk) { DB::table('taggable_taggables')->insertOrIgnore($chunk); } $this->command->info('Seeded tags for ' . $users->count() . ' users.'); } private function seedCalls(): void { $users = User::with(['locations.country', 'locations.division', 'locations.city', 'locations.district']) ->inRandomOrder() ->limit(self::USERS_TO_SEED) ->get(); // Only use tag_ids that exist in taggable_tags AND have a context with a non-null category_id $validTagIds = DB::table('taggable_tags') ->whereIn('tag_id', DB::table('taggable_contexts')->whereNotNull('category_id')->pluck('id')) ->inRandomOrder()->limit(100)->pluck('tag_id')->toArray(); if (empty($validTagIds)) { $this->command->warn('No valid tags found, skipping call seeding.'); return; } // Fetch tag names for generating content $tagNames = DB::table('taggable_tags') ->whereIn('tag_id', $validTagIds) ->pluck('name', 'tag_id'); $count = 0; $now = now(); foreach ($users as $user) { $selected = collect($validTagIds)->shuffle()->take(self::CALLS_PER_USER); // Resolve location from user's primary location $locationId = $this->resolveUserLocation($user); foreach ($selected as $tagId) { // Random till date between 1 and 3 months from now $tillMonths = rand(1, 3); $till = $now->copy()->addMonths($tillMonths); $call = Call::create([ 'callable_id' => $user->id, 'callable_type' => 'App\Models\User', 'tag_id' => $tagId, 'location_id' => $locationId, 'from' => $now->utc(), 'till' => $till, 'is_public' => true, 'is_suppressed' => false, 'is_paused' => false, ]); $tagName = $tagNames[$tagId] ?? 'this skill'; CallTranslation::create([ 'call_id' => $call->id, 'locale' => 'en', 'content' => "Looking to connect with others around {$tagName}. Available for the coming months.", ]); $count++; } } $this->command->info("Seeded {$count} calls for " . $users->count() . ' users.'); } /** * Resolve or create a standalone Location record from the user's primary location. */ private function resolveUserLocation(User $user): ?int { $userLocation = $user->locations->first(); if (!$userLocation) { return null; } $attributes = array_filter([ 'country_id' => $userLocation->country_id ?: null, 'division_id' => $userLocation->division_id ?: null, 'city_id' => $userLocation->city_id ?: null, 'district_id' => $userLocation->district_id ?: null, ]); if (empty($attributes)) { return null; } // Find existing standalone Location (no locatable_id/type) or create one $location = Location::whereNull('locatable_id') ->whereNull('locatable_type') ->where($attributes) ->first(); if (!$location) { $location = new Location($attributes); $location->save(); } return $location->id; } private function reindexAffectedUsers(): void { // Disable queue so indexing happens synchronously with full relationship loading config(['scout.queue' => false]); $cutoff = now()->subMinutes(10)->toDateTimeString(); $userIds = DB::table('taggable_taggables') ->where('taggable_type', 'App\Models\User') ->where('created_at', '>=', $cutoff) ->pluck('taggable_id') ->merge( DB::table('calls')->where('created_at', '>=', $cutoff)->pluck('callable_id') ) ->unique(); $bar = $this->command->getOutput()->createProgressBar($userIds->count()); User::whereIn('id', $userIds)->each(function (User $user) use ($bar) { $user->searchable(); $bar->advance(); }); $bar->finish(); $this->command->newLine(); // Also re-index the calls themselves $this->command->info('Re-indexing calls in Elasticsearch...'); config(['scout.queue' => false]); $callIds = DB::table('calls')->where('created_at', '>=', $cutoff)->pluck('id'); $callBar = $this->command->getOutput()->createProgressBar($callIds->count()); Call::whereIn('id', $callIds)->each(function (Call $call) use ($callBar) { $call->searchable(); $callBar->advance(); }); $callBar->finish(); $this->command->newLine(); } private function rollback(): void { // Remove only the test-seeded data (created recently) $cutoff = now()->subHour()->toDateTimeString(); // Delete call_translations first (FK constraint), then calls $callIds = DB::table('calls') ->where('created_at', '>=', $cutoff) ->pluck('id'); $translationsDeleted = DB::table('call_translations') ->whereIn('call_id', $callIds) ->delete(); $this->command->info("Removed {$translationsDeleted} test call translations."); $deleted = DB::table('calls') ->where('created_at', '>=', $cutoff) ->delete(); $this->command->info("Removed {$deleted} test calls."); $deleted = DB::table('taggable_taggables') ->where('taggable_type', 'App\Models\User') ->where('created_at', '>=', $cutoff) ->delete(); $this->command->info("Removed {$deleted} test tag assignments."); $this->command->info('Re-indexing affected users in Elasticsearch (synchronously)...'); $this->reindexAffectedUsers(); $this->command->info('Rollback complete.'); } }