info("Creating media search indexes for driver: {$driver}"); if ($driver === 'manticore') { return $this->createManticoreIndexes(); } elseif ($driver === 'elasticsearch') { return $this->createElasticsearchIndexes(); } else { $this->error("Unsupported search driver: {$driver}"); return Command::FAILURE; } } /** * Create Manticore indexes for movies and tvshows. */ private function createManticoreIndexes(): int { $host = config('search.drivers.manticore.host', '127.0.0.1'); $port = config('search.drivers.manticore.port', 9308); $client = new Client([ 'host' => $host, 'port' => $port, ]); // Test connection try { $client->nodes()->status(); $this->info('Connected to ManticoreSearch successfully.'); } catch (\Exception $e) { $this->error('Failed to connect to ManticoreSearch: '.$e->getMessage()); return Command::FAILURE; } $dropExisting = $this->option('drop'); // Define movies and tvshows indexes $indexes = [ 'movies_rt' => [ 'settings' => [ 'min_prefix_len' => 0, 'min_infix_len' => 2, ], 'columns' => [ 'imdbid' => ['type' => 'integer'], 'tmdbid' => ['type' => 'integer'], 'traktid' => ['type' => 'integer'], 'title' => ['type' => 'text'], 'year' => ['type' => 'text'], 'genre' => ['type' => 'text'], 'actors' => ['type' => 'text'], 'director' => ['type' => 'text'], 'rating' => ['type' => 'text'], 'plot' => ['type' => 'text'], ], ], 'tvshows_rt' => [ 'settings' => [ 'min_prefix_len' => 0, 'min_infix_len' => 2, ], 'columns' => [ 'title' => ['type' => 'text'], 'tvdb' => ['type' => 'integer'], 'trakt' => ['type' => 'integer'], 'tvmaze' => ['type' => 'integer'], 'tvrage' => ['type' => 'integer'], 'imdb' => ['type' => 'integer'], 'tmdb' => ['type' => 'integer'], 'started' => ['type' => 'text'], 'type' => ['type' => 'integer'], ], ], ]; $hasErrors = false; foreach ($indexes as $indexName => $schema) { if (! $this->createManticoreIndex($client, $indexName, $schema, $dropExisting)) { $hasErrors = true; } } if ($hasErrors) { $this->error('Some errors occurred during index creation.'); return Command::FAILURE; } $this->info('Media indexes created successfully!'); // Optionally populate the indexes if ($this->option('populate')) { $this->info('Populating indexes...'); $this->call('nntmux:populate', [ '--manticore' => true, '--movies' => true, ]); $this->call('nntmux:populate', [ '--manticore' => true, '--tvshows' => true, ]); } return Command::SUCCESS; } /** * Create a single Manticore index. * * @param array $schema */ private function createManticoreIndex(Client $client, string $indexName, array $schema, bool $dropExisting): bool { try { // Check if index exists $indexExists = false; try { $client->table($indexName)->describe(); $indexExists = true; } catch (ResponseException $e) { // Index doesn't exist } if ($indexExists) { if ($dropExisting) { $this->warn("Dropping existing index: {$indexName}"); $client->tables()->drop(['index' => $indexName, 'body' => ['silent' => true]]); } else { $this->info("Index {$indexName} already exists. Use --drop to recreate."); return true; } } // Create the index using the ManticoreSearch PHP client $this->info("Creating index: {$indexName}"); // Use the same format as CreateManticoreIndexes - pass schema directly $response = $client->tables()->create([ 'index' => $indexName, 'body' => $schema, ]); $this->info("Index {$indexName} created successfully."); $this->line('Response: '.json_encode($response, JSON_PRETTY_PRINT)); return true; } catch (ResponseException $e) { // Check if the error is because the index already exists if (str_contains($e->getMessage(), 'already exists')) { $this->warn("Index {$indexName} already exists. Use --drop to recreate."); return true; } // Check for data_dir configuration error if (str_contains($e->getMessage(), 'data_dir')) { $this->error("Failed to create index {$indexName}: ".$e->getMessage()); $this->newLine(); $this->warn('ManticoreSearch requires data_dir to be set in its configuration file.'); $this->info('To fix this, edit your ManticoreSearch config file (usually /etc/manticoresearch/manticore.conf):'); $this->line(' 1. Add or uncomment: data_dir = /var/lib/manticore'); $this->line(' 2. Ensure the directory exists and is writable by the manticore user'); $this->line(' 3. Restart ManticoreSearch: sudo systemctl restart manticore'); return false; } $this->error("Failed to create index {$indexName}: ".$e->getMessage()); return false; } catch (\Throwable $e) { $this->error("Unexpected error creating index {$indexName}: ".$e->getMessage()); return false; } } /** * Create Elasticsearch indexes for movies and tvshows. */ private function createElasticsearchIndexes(): int { $this->info('Creating Elasticsearch media indexes...'); $dropExisting = $this->option('drop'); // Movies index mapping $moviesMapping = [ 'mappings' => [ 'properties' => [ 'imdbid' => ['type' => 'integer'], 'tmdbid' => ['type' => 'integer'], 'traktid' => ['type' => 'integer'], 'title' => ['type' => 'text', 'analyzer' => 'standard'], 'year' => ['type' => 'keyword'], 'genre' => ['type' => 'text'], 'actors' => ['type' => 'text'], 'director' => ['type' => 'text'], 'rating' => ['type' => 'keyword'], 'plot' => ['type' => 'text'], ], ], 'settings' => [ 'number_of_shards' => 1, 'number_of_replicas' => 0, ], ]; // TV shows index mapping $tvshowsMapping = [ 'mappings' => [ 'properties' => [ 'title' => ['type' => 'text', 'analyzer' => 'standard'], 'tvdb' => ['type' => 'integer'], 'trakt' => ['type' => 'integer'], 'tvmaze' => ['type' => 'integer'], 'tvrage' => ['type' => 'integer'], 'imdb' => ['type' => 'integer'], 'tmdb' => ['type' => 'integer'], 'started' => ['type' => 'keyword'], 'type' => ['type' => 'integer'], ], ], 'settings' => [ 'number_of_shards' => 1, 'number_of_replicas' => 0, ], ]; try { // Create Elasticsearch client directly $esConfig = config('search.drivers.elasticsearch'); $esClient = ClientBuilder::create() ->setHosts($esConfig['hosts'] ?? [['host' => 'localhost', 'port' => 9200]]) ->build(); // Create movies index $moviesIndex = config('search.drivers.elasticsearch.indexes.movies', 'movies'); if ($dropExisting && $esClient->indices()->exists(['index' => $moviesIndex])) { $this->warn("Dropping existing index: {$moviesIndex}"); $esClient->indices()->delete(['index' => $moviesIndex]); } if (! $esClient->indices()->exists(['index' => $moviesIndex])) { $this->info("Creating index: {$moviesIndex}"); $esClient->indices()->create([ 'index' => $moviesIndex, 'body' => $moviesMapping, ]); $this->info("Index {$moviesIndex} created successfully."); } else { $this->info("Index {$moviesIndex} already exists. Use --drop to recreate."); } // Create tvshows index $tvshowsIndex = config('search.drivers.elasticsearch.indexes.tvshows', 'tvshows'); if ($dropExisting && $esClient->indices()->exists(['index' => $tvshowsIndex])) { $this->warn("Dropping existing index: {$tvshowsIndex}"); $esClient->indices()->delete(['index' => $tvshowsIndex]); } if (! $esClient->indices()->exists(['index' => $tvshowsIndex])) { $this->info("Creating index: {$tvshowsIndex}"); $esClient->indices()->create([ 'index' => $tvshowsIndex, 'body' => $tvshowsMapping, ]); $this->info("Index {$tvshowsIndex} created successfully."); } else { $this->info("Index {$tvshowsIndex} already exists. Use --drop to recreate."); } $this->info('Elasticsearch media indexes created successfully!'); // Optionally populate the indexes if ($this->option('populate')) { $this->info('Populating indexes...'); $this->call('nntmux:populate', [ '--elastic' => true, '--movies' => true, ]); $this->call('nntmux:populate', [ '--elastic' => true, '--tvshows' => true, ]); } return Command::SUCCESS; } catch (\Throwable $e) { $this->error('Failed to create Elasticsearch indexes: '.$e->getMessage()); return Command::FAILURE; } } }