Skip to content

Commit 0400350

Browse files
authored
feat: add typesense connector (#7282)
1 parent a809ecb commit 0400350

15 files changed

+678
-78
lines changed

.env.example

+11-5
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,20 @@ MAIL_REPLY_TO_ADDRESS=hello@example.com
8282
MAIL_REPLY_TO_NAME="${APP_NAME}"
8383

8484
# Search
85-
# We use Laravel Scout to do full-text search.
86-
# Read config/scout.php for more information.
87-
# Note that you have to use Meilisearch, Algolia or the database driver to enable
88-
# search in Monica. Searching requires a queue to be configured.
85+
## We use Laravel Scout to do full-text search.
86+
## Read config/scout.php for more information.
87+
## Note that you have to use: 'meilisearch', 'typesense', 'algolia' or the 'database'
88+
## driver to enable search in Monica. Searching requires a queue to be configured.
8989
SCOUT_DRIVER=database
9090
SCOUT_QUEUE=true
91-
MEILISEARCH_HOST=
91+
## If you never intend to use the 'database' driver, you can set this value to false:
92+
FULL_TEXT_INDEX=true
93+
## Meilisearch settings
94+
MEILISEARCH_URL=
9295
MEILISEARCH_KEY=
96+
## Typesense settings
97+
TYPESENSE_HOST=
98+
TYPESENSE_API_KEY=
9399

94100
# Notification channels
95101
TELEGRAM_BOT_TOKEN=

.env.example.sail

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ MAIL_REPLY_TO_NAME="${APP_NAME}"
4242

4343
SCOUT_DRIVER=meilisearch
4444
SCOUT_QUEUE=true
45-
MEILISEARCH_HOST=http://meilisearch:7700
45+
MEILISEARCH_URL=http://meilisearch:7700
4646
MEILISEARCH_KEY=
4747
MEILISEARCH_NO_ANALYTICS=false

app/Console/Commands/SetupScout.php

+8-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Console\Commands;
44

5+
use App\Helpers\ScoutHelper;
56
use Illuminate\Console\Command;
67
use Illuminate\Console\ConfirmableTrait;
78
use Symfony\Component\Console\Attribute\AsCommand;
@@ -49,17 +50,18 @@ public function handle(): void
4950
*/
5051
protected function scoutConfigure(): void
5152
{
52-
if (config('scout.driver') === 'meilisearch' && config('scout.meilisearch.host') !== '') {
53-
$this->artisan('☐ Updating indexes on Meilisearch', 'scout:sync-index-settings', ['--verbose' => true]);
53+
if (ScoutHelper::isIndexed()) {
54+
$this->artisan('☐ Updating indexes', 'scout:sync-index-settings', ['--verbose' => true]);
5455
}
5556
}
5657

5758
/**
58-
* Import models.
59+
* Flush indexes.
5960
*/
6061
protected function scoutFlush(): void
6162
{
62-
if (config('scout.driver') !== null && $this->option('flush')) {
63+
if ($this->option('flush') && ScoutHelper::isIndexed()) {
64+
// Using meilisearch config for any driver
6365
foreach (config('scout.meilisearch.index-settings') as $index => $settings) {
6466
$name = (new $index)->getTable();
6567
$this->artisan("☐ Flush {$name} index", 'scout:flush', ['model' => $index, '--verbose' => true]);
@@ -74,7 +76,8 @@ protected function scoutFlush(): void
7476
*/
7577
protected function scoutImport(): void
7678
{
77-
if (config('scout.driver') !== null && $this->option('import')) {
79+
if ($this->option('import') && ScoutHelper::isIndexed()) {
80+
// Using meilisearch config for any driver
7881
foreach (config('scout.meilisearch.index-settings') as $index => $settings) {
7982
$name = (new $index)->getTable();
8083
$this->artisan("☐ Import {$name}", 'scout:import', ['model' => $index, '--verbose' => true]);

app/Helpers/ScoutHelper.php

+61-4
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,84 @@
22

33
namespace App\Helpers;
44

5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Support\Facades\DB;
7+
58
class ScoutHelper
69
{
710
/**
811
* When updating a model, this method determines if we should update the search index.
912
*
10-
* @return bool
11-
*
1213
* @codeCoverageIgnore
1314
*/
14-
public static function activated()
15+
public static function isActivated(): bool
1516
{
1617
switch (config('scout.driver')) {
1718
case 'algolia':
1819
return config('scout.algolia.id') !== '';
1920
case 'meilisearch':
20-
return config('scout.meilisearch.host') !== '';
21+
return config('scout.meilisearch.key') !== '';
22+
case 'typesense':
23+
return config('scout.typesense.client-settings.api_key') !== '';
2124
case 'database':
2225
case 'collection':
2326
return true;
2427
default:
2528
return false;
2629
}
2730
}
31+
32+
/**
33+
* Test if the driver requires indexes.
34+
*
35+
* @codeCoverageIgnore
36+
*/
37+
public static function isIndexed(): bool
38+
{
39+
switch (config('scout.driver')) {
40+
case 'algolia':
41+
return config('scout.algolia.id') !== '';
42+
case 'meilisearch':
43+
return config('scout.meilisearch.key') !== '';
44+
case 'typesense':
45+
return config('scout.typesense.client-settings.api_key') !== '';
46+
default:
47+
return false;
48+
}
49+
}
50+
51+
/**
52+
* Test if the driver supports full text indexes.
53+
*
54+
* @codeCoverageIgnore
55+
*/
56+
public static function isFullTextIndex(): bool
57+
{
58+
return config('scout.full_text_index') && in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql']);
59+
}
60+
61+
/**
62+
* Get id and basic elements of this model.
63+
*
64+
* @codeCoverageIgnore
65+
*/
66+
public static function id(Model $model): array
67+
{
68+
if (config('scout.driver') === 'database') {
69+
return [];
70+
}
71+
72+
$id = $model->getKey();
73+
74+
if ($id !== null && $model->getKeyType() === 'string' || config('scout.driver') === 'typesense') {
75+
$id = (string) $id;
76+
}
77+
78+
return [
79+
'id' => $id,
80+
'vault_id' => (string) $model->getAttribute('vault_id'),
81+
'created_at' => (int) optional($model->getAttribute(Model::CREATED_AT))->timestamp,
82+
'updated_at' => (int) optional($model->getAttribute(Model::UPDATED_AT))->timestamp,
83+
];
84+
}
2885
}

app/Models/Contact.php

+10-15
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
use Illuminate\Support\Arr;
2121
use Illuminate\Support\Facades\Auth;
2222
use Laravel\Scout\Attributes\SearchUsingFullText;
23-
use Laravel\Scout\Attributes\SearchUsingPrefix;
2423
use Laravel\Scout\Searchable;
2524

2625
class Contact extends VCardResource
@@ -83,22 +82,18 @@ class Contact extends VCardResource
8382
/**
8483
* Get the indexable data array for the model.
8584
*
86-
*
8785
* @codeCoverageIgnore
8886
*/
89-
#[SearchUsingPrefix(['id', 'vault_id'])]
90-
#[SearchUsingFullText(['first_name', 'last_name', 'middle_name', 'nickname', 'maiden_name'])]
87+
#[SearchUsingFullText(['first_name', 'last_name', 'middle_name', 'nickname', 'maiden_name'], ['expanded' => true])]
9188
public function toSearchableArray(): array
9289
{
93-
return [
94-
'id' => $this->id,
95-
'vault_id' => $this->vault_id,
96-
'first_name' => $this->first_name,
97-
'last_name' => $this->last_name,
98-
'middle_name' => $this->middle_name,
99-
'nickname' => $this->nickname,
100-
'maiden_name' => $this->maiden_name,
101-
];
90+
return array_merge(ScoutHelper::id($this), [
91+
'first_name' => $this->first_name ?? '',
92+
'last_name' => $this->last_name ?? '',
93+
'middle_name' => $this->middle_name ?? '',
94+
'nickname' => $this->nickname ?? '',
95+
'maiden_name' => $this->maiden_name ?? '',
96+
]);
10297
}
10398

10499
/**
@@ -120,7 +115,7 @@ public function scopeActive(Builder $query): Builder
120115
}
121116

122117
/**
123-
* Used to delete related objects from Meilisearch/Algolia instance.
118+
* Used to delete related objects from scout driver instance.
124119
*/
125120
protected static function boot(): void
126121
{
@@ -138,7 +133,7 @@ protected static function boot(): void
138133
*/
139134
public function searchIndexShouldBeUpdated()
140135
{
141-
return ScoutHelper::activated();
136+
return ScoutHelper::isActivated();
142137
}
143138

144139
/**

app/Models/Group.php

+5-10
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use Illuminate\Database\Eloquent\Relations\MorphOne;
1313
use Illuminate\Database\Eloquent\SoftDeletes;
1414
use Laravel\Scout\Attributes\SearchUsingFullText;
15-
use Laravel\Scout\Attributes\SearchUsingPrefix;
1615
use Laravel\Scout\Searchable;
1716

1817
class Group extends VCardResource
@@ -53,18 +52,14 @@ public function uniqueIds()
5352
/**
5453
* Get the indexable data array for the model.
5554
*
56-
*
5755
* @codeCoverageIgnore
5856
*/
59-
#[SearchUsingPrefix(['id', 'vault_id'])]
60-
#[SearchUsingFullText(['name'])]
57+
#[SearchUsingFullText(['name'], ['expanded' => true])]
6158
public function toSearchableArray(): array
6259
{
63-
return [
64-
'id' => $this->id,
65-
'vault_id' => $this->vault_id,
66-
'name' => $this->name,
67-
];
60+
return array_merge(ScoutHelper::id($this), [
61+
'name' => $this->name ?? '',
62+
]);
6863
}
6964

7065
/**
@@ -74,7 +69,7 @@ public function toSearchableArray(): array
7469
*/
7570
public function searchIndexShouldBeUpdated()
7671
{
77-
return ScoutHelper::activated();
72+
return ScoutHelper::isActivated();
7873
}
7974

8075
/**

app/Models/Loan.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class Loan extends Model
5757
*/
5858
public function searchIndexShouldBeUpdated()
5959
{
60-
return ScoutHelper::activated();
60+
return ScoutHelper::isActivated();
6161
}
6262

6363
/**

app/Models/Note.php

+7-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Illuminate\Database\Eloquent\Relations\BelongsTo;
99
use Illuminate\Database\Eloquent\Relations\MorphOne;
1010
use Laravel\Scout\Attributes\SearchUsingFullText;
11-
use Laravel\Scout\Attributes\SearchUsingPrefix;
1211
use Laravel\Scout\Searchable;
1312

1413
class Note extends Model
@@ -33,20 +32,16 @@ class Note extends Model
3332
/**
3433
* Get the indexable data array for the model.
3534
*
36-
*
3735
* @codeCoverageIgnore
3836
*/
39-
#[SearchUsingPrefix(['id', 'vault_id'])]
40-
#[SearchUsingFullText(['title', 'body'])]
37+
#[SearchUsingFullText(['title', 'body'], ['expanded' => true])]
4138
public function toSearchableArray(): array
4239
{
43-
return [
44-
'id' => $this->id,
45-
'vault_id' => $this->vault_id,
46-
'contact_id' => $this->contact_id,
47-
'title' => $this->title,
48-
'body' => $this->body,
49-
];
40+
return array_merge(ScoutHelper::id($this), [
41+
'contact_id' => (string) $this->contact_id,
42+
'title' => $this->title ?? '',
43+
'body' => $this->body ?? '',
44+
]);
5045
}
5146

5247
/**
@@ -56,7 +51,7 @@ public function toSearchableArray(): array
5651
*/
5752
public function searchIndexShouldBeUpdated()
5853
{
59-
return ScoutHelper::activated();
54+
return ScoutHelper::isActivated();
6055
}
6156

6257
/**

app/Models/Vault.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class Vault extends Model
6767
];
6868

6969
/**
70-
* Used to delete related objects from Meilisearch/Algolia instance.
70+
* Used to delete related objects from scout driver instance.
7171
*/
7272
protected static function boot(): void
7373
{

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"stevebauman/location": "^7.0",
4444
"thecodingmachine/safe": "^2.5",
4545
"tightenco/ziggy": "2.3.0",
46+
"typesense/typesense-php": "^4.9",
4647
"uploadcare/uploadcare-php": "^4.1"
4748
},
4849
"require-dev": {

0 commit comments

Comments
 (0)