diff --git a/src/Api/Controllers/SearchController.php b/src/Api/Controllers/SearchController.php index bf66b94..938a3f7 100644 --- a/src/Api/Controllers/SearchController.php +++ b/src/Api/Controllers/SearchController.php @@ -48,6 +48,7 @@ class SearchController extends ListDiscussionsController 'lastPostedAt' => 'updated_at', 'createdAt' => 'created_at', 'commentCount' => 'comment_count', + 'view_count' => 'view_count', ]; protected Collection $searchers; @@ -107,9 +108,19 @@ class SearchController extends ListDiscussionsController $this->addFilters($filterQuery, $actor, $filters) ); + $knownSortFields = array_merge(array_values($this->translateSort), ['rawId']); + foreach ($this->extractSort($request) as $field => $direction) { - $field = $this->translateSort[$field] ?? $field; - $builder->addSort(new Sort($field, $direction)); + $translated = $this->translateSort[$field] ?? $field; + + if (!in_array($translated, $knownSortFields)) { + resolve(\Psr\Log\LoggerInterface::class)->warning( + "blomstra/search: unknown sort field \"{$field}\", ignoring." + ); + continue; + } + + $builder->addSort(new Sort($translated, $direction)); } $response = $builder->search(); diff --git a/src/Commands/BuildCommand.php b/src/Commands/BuildCommand.php index 7de5ad5..8941432 100644 --- a/src/Commands/BuildCommand.php +++ b/src/Commands/BuildCommand.php @@ -13,7 +13,7 @@ namespace Blomstra\Search\Commands; use Blomstra\Search\Jobs\Job; -use Blomstra\Search\Jobs\SavingJob; +use Blomstra\Search\Jobs\UpdateSearchJob; use Blomstra\Search\Seeders\Seeder; use Elasticsearch\Client; use Flarum\Settings\SettingsRepositoryInterface; @@ -71,6 +71,7 @@ class BuildCommand extends Command 'recipient_groups' => ['type' => 'integer'], 'recipient_users' => ['type' => 'integer'], 'comment_count' => ['type' => 'integer'], + 'view_count' => ['type' => 'integer'], ], ]; @@ -181,7 +182,7 @@ class BuildCommand extends Command } if ($collection->isNotEmpty()) { - $queue->pushOn(Job::$onQueue, new SavingJob($collection, $seeder)); + $queue->pushOn(Job::$onQueue, new UpdateSearchJob($collection, $seeder)); } $this->info("IDs {$rangeFrom}–{$rangeTo} | type: {$seeder->type()} | queued: {$collection->count()}."); diff --git a/src/Jobs/SavingJob.php b/src/Jobs/UpdateSearchJob.php similarity index 97% rename from src/Jobs/SavingJob.php rename to src/Jobs/UpdateSearchJob.php index 9aec901..3116faa 100644 --- a/src/Jobs/SavingJob.php +++ b/src/Jobs/UpdateSearchJob.php @@ -17,7 +17,7 @@ use Elasticsearch\Client; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; -class SavingJob extends Job +class UpdateSearchJob extends Job { public function handle(Client $client) { diff --git a/src/Jobs/ViewsSearchJob.php b/src/Jobs/ViewsSearchJob.php new file mode 100644 index 0000000..aa174c7 --- /dev/null +++ b/src/Jobs/ViewsSearchJob.php @@ -0,0 +1,53 @@ +index = resolve('blomstra.search.elastic_index'); + + if (Job::$onQueue) { + $this->onQueue(Job::$onQueue); + } + } + + public function handle(Client $client): void + { + $discussion = Discussion::find($this->discussionId); + + if (!$discussion) { + return; + } + + $type = resolve(DiscussionSerializer::class)->getType(new Discussion()); + + $client->update([ + 'index' => $this->index, + 'id' => "$type:{$this->discussionId}", + 'retry_on_conflict' => 3, + 'ignore' => [404], + 'body' => [ + 'doc' => ['view_count' => (int) $discussion->view_count], + ], + ]); + } +} diff --git a/src/Provider.php b/src/Provider.php index f8f45b2..29b38f9 100644 --- a/src/Provider.php +++ b/src/Provider.php @@ -14,7 +14,8 @@ namespace Blomstra\Search; use Blomstra\Search\Jobs\DeletingJob; use Blomstra\Search\Jobs\Job; -use Blomstra\Search\Jobs\SavingJob; +use Blomstra\Search\Jobs\UpdateSearchJob; +use Blomstra\Search\Jobs\ViewsSearchJob; use Elasticsearch\Client as Elastic; use Elasticsearch\ClientBuilder; use Flarum\Api\Client; @@ -108,12 +109,18 @@ class Provider extends AbstractServiceProvider /** @var string|Seeders\Seeder $seeder */ foreach ($seeders as $seeder) { $seeder::savingOn($events, function ($model) use ($queue, $seeder) { - $queue->pushOn(Job::$onQueue, new SavingJob(Collection::make([$model]), $seeder)); + $queue->pushOn(Job::$onQueue, new UpdateSearchJob(Collection::make([$model]), $seeder)); }); $seeder::deletingOn($events, function ($model) use ($queue, $seeder) { $queue->pushOn(Job::$onQueue, new DeletingJob(Collection::make([$model]), $seeder)); }); + + if (method_exists($seeder, 'viewingOn')) { + $seeder::viewingOn($events, function (int $discussionId) use ($queue) { + $queue->pushOn(Job::$onQueue, new ViewsSearchJob($discussionId)); + }); + } } } } diff --git a/src/Seeders/DiscussionSeeder.php b/src/Seeders/DiscussionSeeder.php index 4f28de0..3efdd55 100644 --- a/src/Seeders/DiscussionSeeder.php +++ b/src/Seeders/DiscussionSeeder.php @@ -16,7 +16,9 @@ use Blomstra\Search\Save\Document; use Flarum\Api\Serializer\DiscussionSerializer; use Flarum\Discussion\Discussion; use Flarum\Discussion\Event as Core; +use Flarum\Extension\ExtensionManager; use FoF\Byobu\Events as Byobu; +use FoF\DiscussionViews\Events\DiscussionWasViewed; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; @@ -58,6 +60,27 @@ class DiscussionSeeder extends Seeder }); } + public static function viewingOn(Dispatcher $events, callable $callable): void + { + if (!resolve(ExtensionManager::class)->isEnabled('fof-discussion-views')) { + return; + } + + $events->listen(DiscussionWasViewed::class, function (DiscussionWasViewed $event) use ($callable) { + $viewCount = $event->discussion->view_count; + + $shouldSync = match (true) { + $viewCount < 15 => true, + $viewCount < 100 => rand(1, 3) === 1, + default => rand(1, 19) === 1, + }; + + if ($shouldSync) { + $callable($event->discussion->id); + } + }); + } + public static function deletingOn(Dispatcher $events, callable $callable) { $events->listen([ @@ -107,6 +130,10 @@ class DiscussionSeeder extends Seeder $document['is_sticky'] = (bool) $model->is_sticky; } + if ($this->extensionEnabled('fof-discussion-views')) { + $document['view_count'] = (int) ($model->view_count ?? 0); + } + return $document; } }