moved to elastic

This commit is contained in:
Daniel Klabbers 2021-10-14 12:31:45 +02:00
parent e2e56831e5
commit f69541d38a
9 changed files with 96 additions and 51 deletions

View File

@ -21,10 +21,8 @@
],
"require": {
"php": ">= 8.0",
"flarum/core": "^1.0.0"
},
"suggest": {
"meilisearch/meilisearch-php": "Required to use the MeiliSearch engine (^0.19)."
"flarum/core": "^1.0.0",
"elasticsearch/elasticsearch": "7.*"
},
"extra": {
"flarum-extension": {

2
js/dist/forum.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -64,7 +64,7 @@ export default class DiscussionsSearchSource implements SearchSource {
return (
<>
<li className="Dropdown-header">{app.translator.trans('core.forum.search.discussions_heading')}</li>,
<li className="Dropdown-header">{app.translator.trans('core.forum.search.discussions_heading')}</li>
<li>
<LinkButton icon="fas fa-search" href={app.route('index', { q: query })}>
{app.translator.trans('core.forum.search.all_discussions_button', { query })}

View File

@ -3,6 +3,7 @@
namespace Blomstra\Search\Api\Controllers;
use Blomstra\Search\Schemas\Schema;
use Elasticsearch\Client;
use Flarum\Api\Controller\AbstractListController;
use Flarum\Extension\ExtensionManager;
use Flarum\Group\Group;
@ -11,17 +12,16 @@ use Flarum\User\User;
use Illuminate\Contracts\Container\Container;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use MeiliSearch\Client;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
class SearchController extends AbstractListController
{
public function __construct(protected Client $meili)
{}
protected function data(ServerRequestInterface $request, Document $document)
{
/** @var Client $client */
$client = resolve('blomstra.search.elastic');
$index = Arr::get($request->getQueryParams(), 'index');
$actor = RequestUtil::getActor($request);
@ -30,20 +30,26 @@ class SearchController extends AbstractListController
$schema = $this->getSchema($index);
$result = $this->meili->index($index)->search($filters['q'], [
'offset' => $this->extractOffset($request),
'limit' => $this->extractLimit($request),
$result = $client->search([
'index' => $index,
'from' => $this->extractOffset($request),
'size' => $this->extractLimit($request),
'sort' => $this->extractSort($request),
// Filters based on permissions etc..
'filter' => $this->getFilters($actor)
'body' => [
'query' => [
'multi_match' => [
'query' => $filters['q'],
'fields' => array_keys($schema->fulltext(new ($schema::model())))
]
]
]
]);
$this->serializer = $schema::serializer();
return Collection::make($result->getHits())
->map(function ($hit) use ($schema) {
return $schema::model()::query()->find($hit['id']);
});
$ids = Collection::make(Arr::get($result, 'hits.hits'))->pluck('_id')->toArray();
return $schema::model()::query()->findMany($ids);
}
protected function getSchema(string $index): ?Schema

View File

@ -4,12 +4,12 @@ namespace Blomstra\Search\Commands;
use Blomstra\Search\Observe\SavingJob;
use Blomstra\Search\Schemas\Schema;
use Elasticsearch\Client;
use Illuminate\Console\Command;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Queue\Queue;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use MeiliSearch\Client;
class RebuildDocumentsCommand extends Command
{
@ -24,8 +24,8 @@ class RebuildDocumentsCommand extends Command
/** @var Queue $queue */
$queue = $container->make(Queue::class);
/** @var Client $meili */
$meili = $container->make(Client::class);
/** @var Client $client */
$client = $container->make('blomstra.search.elastic');
/** @var Schema $schema */
foreach ($schemas as $schema) {
@ -33,13 +33,21 @@ class RebuildDocumentsCommand extends Command
$model = $schema::model();
// Flush the index.
if ($this->option('flush')) $meili->index($schema::index())->delete();
if ($this->option('flush')) $client->indices()->delete([
'index' => $schema::index()
]);
$model::query()->chunk(50, function (Collection $collection) use ($model, $queue) {
$total = 0;
$model::query()->chunk(50, function (Collection $collection) use ($model, $queue, &$total) {
$queue->push(new SavingJob($model, $collection));
$this->info("Pushed {$collection->count()} into the index");
$this->info("Pushed {$collection->count()} into the index.");
$total += $collection->count();
});
$this->info("Pushed a total of $total into the index.");
}
}
}

View File

@ -2,41 +2,70 @@
namespace Blomstra\Search\Observe;
use Elasticsearch\Client;
use Illuminate\Contracts\Container\Container;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use MeiliSearch\Client;
class SavingJob extends Job
{
public function __construct(protected string $class, protected Collection $models)
{}
public function handle(Client $meili)
public function handle(Container $container)
{
/** @var Client $client */
$client = $container->make('blomstra.search.elastic');
$schema = $this->getSchema();
if (! $schema) return;
if($first = $this->models->first()) {
$properties = [];
foreach (array_keys($schema->fulltext($first)) as $key) {
$properties[$key] = ['type' => 'text'];
}
// Set up the index
$meili->index($schema::index())->updateSettings([
'filterableAttributes' => array_keys($schema->filters($first)),
'searchableAttributes' => array_keys($schema->fulltext($first)),
]);
// if (! $client->indices()->exists([
// 'index' => $schema::index(),
// 'expand_wildcards' => 'none'
// ])) {
// $client->indices()->create([
// 'index' => $schema::index(),
// 'body' => [
// 'mappings' => [
// 'properties' => $properties
// ],
// 'settings' => [
// 'index' => [
// 'query' => [
// 'default_field' => array_keys($schema->fulltext($first))
// ]
// ]
// ]
// ]
// ]);
// }
}
// Preparing body for storing.
$body = $this->models->map(function (Model $model) use ($schema) {
return array_merge(
$schema->fulltext($model),
$schema->filters($model), [
$model->getKeyName() => $model->getKey()
]);
});
return [
['index' => ['_id' => $model->getKey(), '_index' => $schema::index()]],
array_merge(
$schema->fulltext($model),
$schema->filters($model))
$meili->index($schema::index())->addDocuments(
$body->toArray(),
(new $this->class)->getKeyName()
);
];
})->flatten(1);
$response = $client->bulk([
'index' => $schema::index(),
'body' => $body->toArray(),
'refresh' => true
]);
}
}

View File

@ -6,13 +6,14 @@ use Blomstra\Search\Observe\DeletingJob;
use Blomstra\Search\Observe\SavingJob;
use Blomstra\Search\Schemas\DiscussionSchema;
use Blomstra\Search\Schemas\Schema;
use Elasticsearch\ClientBuilder;
use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Queue\Queue;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use MeiliSearch\Client;
use Psr\Log\LoggerInterface;
class Provider extends AbstractServiceProvider
{
@ -20,16 +21,16 @@ class Provider extends AbstractServiceProvider
{
$this->container->tag([DiscussionSchema::class], 'blomstra.search.schemas');
$this->container->singleton(Client::class, function (Container $container) {
$this->container->singleton('blomstra.search.elastic', function (Container $container) {
$config = $container->make('flarum.config') ?? [];
$meili = Arr::get($config, 'meili');
$elastic = Arr::get($config, 'elastic');
if (! $meili) {
return null;
}
return new Client($meili);
return ClientBuilder::create()
->setHosts([$elastic['endpoint']])
->setBasicAuthentication($elastic['username'], $elastic['password'])
->setLogger($container->make(LoggerInterface::class))
->build();
});
}

View File

@ -44,10 +44,13 @@ class DiscussionSchema extends Schema
})->flatten();
}
if (! $permissions || $permissions->isEmpty()) {
if (! $discussion->is_private && (! $permissions || $permissions->isEmpty())) {
$permissions = Permission::query()
->where('permission', 'viewForum')
->pluck('group_id');
} else {
$permissions = collect();
}
$filters['groups'] = $permissions->toArray();