improved full-text search

- added support for typesense 0.24.1
- fixed issue where empty `filter_by` field being sent to typesense which resulted in an error
- added infix indexing for title fields in case of Anime/Manga
- added more env vars for customising typesense search
- upgraded typesense driver
This commit is contained in:
pushrbx 2023-05-20 23:23:51 +01:00
parent 6eff2af172
commit dc9b234f19
7 changed files with 177 additions and 15 deletions

View File

@ -273,6 +273,70 @@ class Anime extends JikanApiSearchableModel
];
}
public function getCollectionSchema(): array
{
return [
'name' => $this->searchableAs(),
'fields' => [
[
'name' => '.*',
'type' => 'auto',
],
[
'name' => 'title',
'type' => 'string',
'optional' => false,
'infix' => true,
'sort' => true
],
[
'name' => 'title_transformed',
'type' => 'string',
'optional' => false,
'infix' => true,
'sort' => true
],
[
'name' => 'title_japanese',
'type' => 'string',
'optional' => true,
'locale' => 'jp',
'infix' => true,
'sort' => false
],
[
'name' => 'title_japanese_transformed',
'type' => 'string',
'optional' => true,
'locale' => 'jp',
'infix' => true,
'sort' => false
],
[
'name' => 'title_english',
'type' => 'string',
'optional' => true,
'infix' => true,
'sort' => true
],
[
'name' => 'title_english_transformed',
'type' => 'string',
'optional' => true,
'infix' => true,
'sort' => true
],
[
'name' => 'title_synonyms',
'type' => 'string[]',
'optional' => true,
'infix' => true,
'sort' => false
]
]
];
}
/**
* The fields to be queried against. See https://typesense.org/docs/0.21.0/api/documents.html#search.
*

View File

@ -3,8 +3,10 @@
namespace App\Features;
use App\Dto\AnimeSearchCommand;
use App\Enums\AnimeOrderByEnum;
use App\Http\Resources\V4\AnimeCollection;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
/**
* @extends SearchRequestHandler<AnimeSearchCommand, AnimeCollection>
@ -23,4 +25,14 @@ class AnimeSearchHandler extends SearchRequestHandler
{
return new AnimeCollection($paginator);
}
protected function prepareOrderByParam(Collection $requestData): Collection
{
if ($requestData->has("q") && !$requestData->has("order_by")) {
// default order by should be popularity, as MAL seems to use this trick.
$requestData->offsetSet("order_by", AnimeOrderByEnum::popularity());
}
return parent::prepareOrderByParam($requestData);
}
}

View File

@ -3,8 +3,10 @@
namespace App\Features;
use App\Dto\MangaSearchCommand;
use App\Enums\MangaOrderByEnum;
use App\Http\Resources\V4\MangaCollection;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
/**
* @extends SearchRequestHandler<MangaSearchCommand, MangaCollection>
@ -26,4 +28,14 @@ class MangaSearchHandler extends SearchRequestHandler
{
return new MangaCollection($paginator);
}
protected function prepareOrderByParam(Collection $requestData): Collection
{
if ($requestData->has("q") && !$requestData->has("order_by")) {
// default order by should be popularity, as MAL seems to use this trick.
$requestData->offsetSet("order_by", MangaOrderByEnum::popularity());
}
return parent::prepareOrderByParam($requestData);
}
}

View File

@ -56,7 +56,6 @@ abstract class SearchRequestHandler implements RequestHandler
if ($requestData->has('order_by') && $requestData->has('q')) {
$requestData->offsetSet("order_by", $requestData->get("order_by")->label);
return $requestData;
}
$requestData->offsetSet("order_by", 'mal_id');

View File

@ -180,6 +180,70 @@ class Manga extends JikanApiSearchableModel
];
}
public function getCollectionSchema(): array
{
return [
'name' => $this->searchableAs(),
'fields' => [
[
'name' => '.*',
'type' => 'auto',
],
[
'name' => 'title',
'type' => 'string',
'optional' => false,
'infix' => true,
'sort' => true
],
[
'name' => 'title_transformed',
'type' => 'string',
'optional' => false,
'infix' => true,
'sort' => true
],
[
'name' => 'title_japanese',
'type' => 'string',
'optional' => true,
'locale' => 'jp',
'infix' => true,
'sort' => false
],
[
'name' => 'title_japanese_transformed',
'type' => 'string',
'optional' => true,
'locale' => 'jp',
'infix' => true,
'sort' => false
],
[
'name' => 'title_english',
'type' => 'string',
'optional' => true,
'infix' => true,
'sort' => true
],
[
'name' => 'title_english_transformed',
'type' => 'string',
'optional' => true,
'infix' => true,
'sort' => true
],
[
'name' => 'title_synonyms',
'type' => 'string[]',
'optional' => true,
'infix' => true,
'sort' => false
]
]
];
}
/** @noinspection PhpUnused */
public function getThemesAttribute()
{

View File

@ -35,6 +35,14 @@ class TypeSenseScoutSearchService implements ScoutSearchService
// in the query exhaustively, without stopping early when enough results are found.
$options['exhaustive_search'] = env('TYPESENSE_SEARCH_EXHAUSTIVE', "true");
$options['search_cutoff_ms'] = (int) env('TYPESENSE_SEARCH_CUTOFF_MS', 450);
// this will be ignored together with exhaustive_search set to "true"
$options['drop_tokens_threshold'] = (int) env('TYPESENSE_DROP_TOKENS_THRESHOLD', 1);
$options['typo_tokens_threshold'] = (int) env('TYPESENSE_TYPO_TOKENS_THRESHOLD', 1);
// prevent `Could not parse the filter query: unbalanced `&&` operands.` error
// this adds support for typesense v0.24.1
if ($options['filter_by'] === ' && ' || $options['filter_by'] === '&&') {
unset($options['filter_by']);
}
if (array_key_exists('per_page', $options) && $options['per_page'] > 250) {
$options['per_page'] = min($this->maxItemsPerPage, 250);

31
composer.lock generated
View File

@ -11101,32 +11101,34 @@
},
{
"name": "typesense/laravel-scout-typesense-driver",
"version": "v5.1.0",
"version": "v5.2.5",
"source": {
"type": "git",
"url": "https://github.com/typesense/laravel-scout-typesense-driver.git",
"reference": "d5b488ad8968989d9425e4b58aceb9231b569440"
"reference": "026187e037a91c1532ecd1d209ae03c7e8733a40"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/typesense/laravel-scout-typesense-driver/zipball/d5b488ad8968989d9425e4b58aceb9231b569440",
"reference": "d5b488ad8968989d9425e4b58aceb9231b569440",
"url": "https://api.github.com/repos/typesense/laravel-scout-typesense-driver/zipball/026187e037a91c1532ecd1d209ae03c7e8733a40",
"reference": "026187e037a91c1532ecd1d209ae03c7e8733a40",
"shasum": ""
},
"require": {
"illuminate/bus": "^7.0|^8.0|^9.0",
"illuminate/contracts": "^7.0|^8.0|^9.0",
"illuminate/database": "^7.0|^8.0|^9.0",
"illuminate/pagination": "^7.0|^8.0|^9.0",
"illuminate/queue": "^7.0|^8.0|^9.0",
"illuminate/support": "^7.0|^8.0|^9.0",
"illuminate/bus": "^7.0|^8.0|^9.0|^10.0",
"illuminate/contracts": "^7.0|^8.0|^9.0|^10.0",
"illuminate/database": "^7.0|^8.0|^9.0|^10.0",
"illuminate/pagination": "^7.0|^8.0|^9.0|^10.0",
"illuminate/queue": "^7.0|^8.0|^9.0|^10.0",
"illuminate/support": "^7.0|^8.0|^9.0|^10.0",
"laravel/scout": "^8.0|^9.0",
"php": "^8.0",
"typesense/typesense-php": "^4.0"
},
"require-dev": {
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^8.0|^9.0"
"orchestra/testbench": "^6.17|^7.0|^8.0",
"phpunit/phpunit": "^8.0|^9.0",
"symfony/http-client": "^5.4"
},
"suggest": {
"typesense/typesense-php": "Required to use the Typesense php client."
@ -11144,7 +11146,8 @@
},
"autoload": {
"psr-4": {
"Typesense\\LaravelTypesense\\": "src/"
"Typesense\\LaravelTypesense\\": "src/",
"Typesense\\LaravelTypesense\\Tests\\": "tests/"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -11173,7 +11176,7 @@
],
"support": {
"issues": "https://github.com/typesense/laravel-scout-typesense-driver/issues",
"source": "https://github.com/typesense/laravel-scout-typesense-driver/tree/v5.1.0"
"source": "https://github.com/typesense/laravel-scout-typesense-driver/tree/v5.2.5"
},
"funding": [
{
@ -11181,7 +11184,7 @@
"type": "github"
}
],
"time": "2022-07-20T18:55:28+00:00"
"time": "2023-05-18T07:06:25+00:00"
},
{
"name": "typesense/typesense-php",