moved to elastic
This commit is contained in:
parent
e2e56831e5
commit
f69541d38a
|
|
@ -21,10 +21,8 @@
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">= 8.0",
|
"php": ">= 8.0",
|
||||||
"flarum/core": "^1.0.0"
|
"flarum/core": "^1.0.0",
|
||||||
},
|
"elasticsearch/elasticsearch": "7.*"
|
||||||
"suggest": {
|
|
||||||
"meilisearch/meilisearch-php": "Required to use the MeiliSearch engine (^0.19)."
|
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"flarum-extension": {
|
"flarum-extension": {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -64,7 +64,7 @@ export default class DiscussionsSearchSource implements SearchSource {
|
||||||
|
|
||||||
return (
|
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>
|
<li>
|
||||||
<LinkButton icon="fas fa-search" href={app.route('index', { q: query })}>
|
<LinkButton icon="fas fa-search" href={app.route('index', { q: query })}>
|
||||||
{app.translator.trans('core.forum.search.all_discussions_button', { query })}
|
{app.translator.trans('core.forum.search.all_discussions_button', { query })}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Blomstra\Search\Api\Controllers;
|
namespace Blomstra\Search\Api\Controllers;
|
||||||
|
|
||||||
use Blomstra\Search\Schemas\Schema;
|
use Blomstra\Search\Schemas\Schema;
|
||||||
|
use Elasticsearch\Client;
|
||||||
use Flarum\Api\Controller\AbstractListController;
|
use Flarum\Api\Controller\AbstractListController;
|
||||||
use Flarum\Extension\ExtensionManager;
|
use Flarum\Extension\ExtensionManager;
|
||||||
use Flarum\Group\Group;
|
use Flarum\Group\Group;
|
||||||
|
|
@ -11,17 +12,16 @@ use Flarum\User\User;
|
||||||
use Illuminate\Contracts\Container\Container;
|
use Illuminate\Contracts\Container\Container;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use MeiliSearch\Client;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
class SearchController extends AbstractListController
|
class SearchController extends AbstractListController
|
||||||
{
|
{
|
||||||
public function __construct(protected Client $meili)
|
|
||||||
{}
|
|
||||||
|
|
||||||
protected function data(ServerRequestInterface $request, Document $document)
|
protected function data(ServerRequestInterface $request, Document $document)
|
||||||
{
|
{
|
||||||
|
/** @var Client $client */
|
||||||
|
$client = resolve('blomstra.search.elastic');
|
||||||
|
|
||||||
$index = Arr::get($request->getQueryParams(), 'index');
|
$index = Arr::get($request->getQueryParams(), 'index');
|
||||||
|
|
||||||
$actor = RequestUtil::getActor($request);
|
$actor = RequestUtil::getActor($request);
|
||||||
|
|
@ -30,20 +30,26 @@ class SearchController extends AbstractListController
|
||||||
|
|
||||||
$schema = $this->getSchema($index);
|
$schema = $this->getSchema($index);
|
||||||
|
|
||||||
$result = $this->meili->index($index)->search($filters['q'], [
|
$result = $client->search([
|
||||||
'offset' => $this->extractOffset($request),
|
'index' => $index,
|
||||||
'limit' => $this->extractLimit($request),
|
'from' => $this->extractOffset($request),
|
||||||
|
'size' => $this->extractLimit($request),
|
||||||
'sort' => $this->extractSort($request),
|
'sort' => $this->extractSort($request),
|
||||||
// Filters based on permissions etc..
|
'body' => [
|
||||||
'filter' => $this->getFilters($actor)
|
'query' => [
|
||||||
|
'multi_match' => [
|
||||||
|
'query' => $filters['q'],
|
||||||
|
'fields' => array_keys($schema->fulltext(new ($schema::model())))
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->serializer = $schema::serializer();
|
$this->serializer = $schema::serializer();
|
||||||
|
|
||||||
return Collection::make($result->getHits())
|
$ids = Collection::make(Arr::get($result, 'hits.hits'))->pluck('_id')->toArray();
|
||||||
->map(function ($hit) use ($schema) {
|
|
||||||
return $schema::model()::query()->find($hit['id']);
|
return $schema::model()::query()->findMany($ids);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getSchema(string $index): ?Schema
|
protected function getSchema(string $index): ?Schema
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ namespace Blomstra\Search\Commands;
|
||||||
|
|
||||||
use Blomstra\Search\Observe\SavingJob;
|
use Blomstra\Search\Observe\SavingJob;
|
||||||
use Blomstra\Search\Schemas\Schema;
|
use Blomstra\Search\Schemas\Schema;
|
||||||
|
use Elasticsearch\Client;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Container\Container;
|
use Illuminate\Contracts\Container\Container;
|
||||||
use Illuminate\Contracts\Queue\Queue;
|
use Illuminate\Contracts\Queue\Queue;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use MeiliSearch\Client;
|
|
||||||
|
|
||||||
class RebuildDocumentsCommand extends Command
|
class RebuildDocumentsCommand extends Command
|
||||||
{
|
{
|
||||||
|
|
@ -24,8 +24,8 @@ class RebuildDocumentsCommand extends Command
|
||||||
/** @var Queue $queue */
|
/** @var Queue $queue */
|
||||||
$queue = $container->make(Queue::class);
|
$queue = $container->make(Queue::class);
|
||||||
|
|
||||||
/** @var Client $meili */
|
/** @var Client $client */
|
||||||
$meili = $container->make(Client::class);
|
$client = $container->make('blomstra.search.elastic');
|
||||||
|
|
||||||
/** @var Schema $schema */
|
/** @var Schema $schema */
|
||||||
foreach ($schemas as $schema) {
|
foreach ($schemas as $schema) {
|
||||||
|
|
@ -33,13 +33,21 @@ class RebuildDocumentsCommand extends Command
|
||||||
$model = $schema::model();
|
$model = $schema::model();
|
||||||
|
|
||||||
// Flush the index.
|
// 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));
|
$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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,41 +2,70 @@
|
||||||
|
|
||||||
namespace Blomstra\Search\Observe;
|
namespace Blomstra\Search\Observe;
|
||||||
|
|
||||||
|
use Elasticsearch\Client;
|
||||||
|
use Illuminate\Contracts\Container\Container;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use MeiliSearch\Client;
|
|
||||||
|
|
||||||
class SavingJob extends Job
|
class SavingJob extends Job
|
||||||
{
|
{
|
||||||
public function __construct(protected string $class, protected Collection $models)
|
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();
|
$schema = $this->getSchema();
|
||||||
|
|
||||||
if (! $schema) return;
|
if (! $schema) return;
|
||||||
|
|
||||||
if($first = $this->models->first()) {
|
if($first = $this->models->first()) {
|
||||||
|
$properties = [];
|
||||||
|
|
||||||
|
foreach (array_keys($schema->fulltext($first)) as $key) {
|
||||||
|
$properties[$key] = ['type' => 'text'];
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the index
|
// Set up the index
|
||||||
$meili->index($schema::index())->updateSettings([
|
// if (! $client->indices()->exists([
|
||||||
'filterableAttributes' => array_keys($schema->filters($first)),
|
// 'index' => $schema::index(),
|
||||||
'searchableAttributes' => array_keys($schema->fulltext($first)),
|
// '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.
|
// Preparing body for storing.
|
||||||
$body = $this->models->map(function (Model $model) use ($schema) {
|
$body = $this->models->map(function (Model $model) use ($schema) {
|
||||||
return array_merge(
|
return [
|
||||||
$schema->fulltext($model),
|
['index' => ['_id' => $model->getKey(), '_index' => $schema::index()]],
|
||||||
$schema->filters($model), [
|
array_merge(
|
||||||
$model->getKeyName() => $model->getKey()
|
$schema->fulltext($model),
|
||||||
]);
|
$schema->filters($model))
|
||||||
});
|
|
||||||
|
|
||||||
$meili->index($schema::index())->addDocuments(
|
];
|
||||||
$body->toArray(),
|
})->flatten(1);
|
||||||
(new $this->class)->getKeyName()
|
|
||||||
);
|
$response = $client->bulk([
|
||||||
|
'index' => $schema::index(),
|
||||||
|
'body' => $body->toArray(),
|
||||||
|
'refresh' => true
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@ use Blomstra\Search\Observe\DeletingJob;
|
||||||
use Blomstra\Search\Observe\SavingJob;
|
use Blomstra\Search\Observe\SavingJob;
|
||||||
use Blomstra\Search\Schemas\DiscussionSchema;
|
use Blomstra\Search\Schemas\DiscussionSchema;
|
||||||
use Blomstra\Search\Schemas\Schema;
|
use Blomstra\Search\Schemas\Schema;
|
||||||
|
use Elasticsearch\ClientBuilder;
|
||||||
use Flarum\Foundation\AbstractServiceProvider;
|
use Flarum\Foundation\AbstractServiceProvider;
|
||||||
use Illuminate\Contracts\Container\Container;
|
use Illuminate\Contracts\Container\Container;
|
||||||
use Illuminate\Contracts\Events\Dispatcher;
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
use Illuminate\Contracts\Queue\Queue;
|
use Illuminate\Contracts\Queue\Queue;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use MeiliSearch\Client;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class Provider extends AbstractServiceProvider
|
class Provider extends AbstractServiceProvider
|
||||||
{
|
{
|
||||||
|
|
@ -20,16 +21,16 @@ class Provider extends AbstractServiceProvider
|
||||||
{
|
{
|
||||||
$this->container->tag([DiscussionSchema::class], 'blomstra.search.schemas');
|
$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') ?? [];
|
$config = $container->make('flarum.config') ?? [];
|
||||||
|
|
||||||
$meili = Arr::get($config, 'meili');
|
$elastic = Arr::get($config, 'elastic');
|
||||||
|
|
||||||
if (! $meili) {
|
return ClientBuilder::create()
|
||||||
return null;
|
->setHosts([$elastic['endpoint']])
|
||||||
}
|
->setBasicAuthentication($elastic['username'], $elastic['password'])
|
||||||
|
->setLogger($container->make(LoggerInterface::class))
|
||||||
return new Client($meili);
|
->build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,13 @@ class DiscussionSchema extends Schema
|
||||||
})->flatten();
|
})->flatten();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $permissions || $permissions->isEmpty()) {
|
if (! $discussion->is_private && (! $permissions || $permissions->isEmpty())) {
|
||||||
$permissions = Permission::query()
|
$permissions = Permission::query()
|
||||||
->where('permission', 'viewForum')
|
->where('permission', 'viewForum')
|
||||||
->pluck('group_id');
|
->pluck('group_id');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$permissions = collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$filters['groups'] = $permissions->toArray();
|
$filters['groups'] = $permissions->toArray();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue