From cba65ce6400b09c1bd454ef344fc1a332fa6dd53 Mon Sep 17 00:00:00 2001 From: philipp lang Date: Sun, 26 Feb 2023 20:51:59 +0100 Subject: [PATCH] Add searching for member with scout --- app/Contribution/Actions/FormAction.php | 9 +- app/Http/Views/MemberView.php | 9 +- app/Member/Actions/SearchAction.php | 40 +++++ app/Member/Member.php | 52 +++--- composer.json | 1 + composer.lock | 165 +++++++++++++----- config/scout.php | 142 +++++++++++++++ ...26_192658_create_members_search_column.php | 33 ++++ package-lock.json | 156 +++++++++++++++-- package.json | 2 + packages/laravel-nami | 2 +- resources/js/app.js | 3 + resources/js/views/contribution/VIndex.vue | 60 ++++--- routes/web.php | 2 + tests/Feature/Contribution/IndexTest.php | 10 +- tests/Feature/Member/SearchTest.php | 28 +++ 16 files changed, 586 insertions(+), 128 deletions(-) create mode 100644 app/Member/Actions/SearchAction.php create mode 100644 config/scout.php create mode 100644 database/migrations/2023_02_26_192658_create_members_search_column.php create mode 100644 tests/Feature/Member/SearchTest.php diff --git a/app/Contribution/Actions/FormAction.php b/app/Contribution/Actions/FormAction.php index bf3dab25..e7ef6f76 100644 --- a/app/Contribution/Actions/FormAction.php +++ b/app/Contribution/Actions/FormAction.php @@ -4,8 +4,6 @@ namespace App\Contribution\Actions; use App\Contribution\ContributionFactory; use App\Country; -use App\Member\Member; -use App\Member\MemberResource; use Inertia\Inertia; use Inertia\Response; use Lorisleiva\Actions\Concerns\AsAction; @@ -20,9 +18,10 @@ class FormAction public function handle(): array { return [ - 'allMembers' => MemberResource::collection(Member::slangOrdered()->get()), - 'countries' => Country::pluck('name', 'id'), - 'defaultCountry' => Country::firstWhere('name', 'Deutschland')->id, + 'countries' => Country::select('name', 'id')->get(), + 'data' => [ + 'country' => Country::firstWhere('name', 'Deutschland')->id, + ], 'compilers' => app(ContributionFactory::class)->compilerSelect(), ]; } diff --git a/app/Http/Views/MemberView.php b/app/Http/Views/MemberView.php index 3183c892..72ec1570 100644 --- a/app/Http/Views/MemberView.php +++ b/app/Http/Views/MemberView.php @@ -19,13 +19,12 @@ class MemberView $activities = Activity::with('subactivities')->get(); return [ - 'data' => MemberResource::collection(Member::select('*') - ->filter($filter)->search($request->query('search', null)) + 'data' => MemberResource::collection(Member::search($request->search)->query(fn ($q) => $q->select('*') + ->filter($filter) ->with('payments.subscription')->with('memberships')->with('courses')->with('subscription')->with('leaderMemberships')->with('ageGroupMemberships') ->withPendingPayment() - ->orderByRaw('lastname, firstname') - ->paginate(15) - ), + ->ordered() + )->paginate(15)), 'filterActivities' => Activity::where('is_filterable', true)->pluck('name', 'id'), 'filterSubactivities' => Subactivity::where('is_filterable', true)->pluck('name', 'id'), 'toolbar' => [['href' => route('member.index'), 'label' => 'Zurück', 'color' => 'primary', 'icon' => 'plus']], diff --git a/app/Member/Actions/SearchAction.php b/app/Member/Actions/SearchAction.php new file mode 100644 index 00000000..83abb6d3 --- /dev/null +++ b/app/Member/Actions/SearchAction.php @@ -0,0 +1,40 @@ + + */ + public function handle(string $search): Collection + { + return Member::search($search)->query(fn ($query) => $query->ordered())->get(); + } + + public function asController(ActionRequest $request): AnonymousResourceCollection + { + if ($request->input('minLength') !== null && strlen($request->input('search')) < $request->input('minLength')) { + return MemberResource::collection($this->empty()); + } + + return MemberResource::collection($this->handle($request->input('search', ''))); + } + + /** + * @return Collection + */ + private function empty(): Collection + { + return Member::where('id', -1)->get(); + } +} diff --git a/app/Member/Member.php b/app/Member/Member.php index 5e2a804a..49bf9bed 100644 --- a/app/Member/Member.php +++ b/app/Member/Member.php @@ -23,9 +23,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Notifications\Notifiable; +use Laravel\Scout\Attributes\SearchUsingFullText; use Sabre\VObject\Component\VCard; use Sabre\VObject\Reader; use Spatie\LaravelData\Lazy; +use Laravel\Scout\Searchable; /** * @property string $subscription_name @@ -37,6 +39,7 @@ class Member extends Model use HasNamiField; use HasFactory; use Sluggable; + use Searchable; /** * @var array @@ -46,7 +49,7 @@ class Member extends Model /** * @var array */ - public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug']; + public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug', 'search']; /** * @var array @@ -83,23 +86,6 @@ class Member extends Model ]; } - /** - * @param Builder $query - * @return Builder - */ - public function scopeSearch(Builder $query, ?string $text): Builder - { - if (is_null($text)) { - return $query; - } - - return $query->where('firstname', 'LIKE', '%'.$text.'%') - ->orWhere('lastname', 'LIKE', '%'.$text.'%') - ->orWhere('address', 'LIKE', '%'.$text.'%') - ->orWhere('zip', 'LIKE', '%'.$text.'%') - ->orWhere('location', 'LIKE', '%'.$text.'%'); - } - // ---------------------------------- Actions ---------------------------------- public function syncVersion(): void { @@ -289,6 +275,8 @@ class Member extends Model $model->payments->each->delete(); $model->memberships->each->delete(); }); + + static::saving(fn ($model) => $model->updateSearch()); } // ---------------------------------- Scopes ----------------------------------- @@ -301,15 +289,6 @@ class Member extends Model return $query->orderByRaw('lastname, firstname'); } - /** - * @param Builder $query - * @return Builder - */ - public function scopeSlangOrdered(Builder $query): Builder - { - return $query->orderByRaw('firstname, lastname'); - } - /** * @param Builder $query * @return Builder @@ -496,4 +475,23 @@ class Member extends Model 'mglnr' => Lazy::create(fn () => 'Mglnr.: '.$this->nami_id), ]); } + + /** + * @return array + */ + #[SearchUsingFullText(['search_text'])] + public function toSearchableArray(): array + { + return [ + 'search_text' => $this->search_text ?: '', + ]; + } + + public function updateSearch(): void + { + $this->attributes['search_text'] = collect([ + $this->fullname, + $this->fullAddress, + ])->implode(' '); + } } diff --git a/composer.json b/composer.json index b721f9b4..87bace71 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "inertiajs/inertia-laravel": "^0.2.5", "laravel/framework": "^9.0", "laravel/horizon": "^5.0", + "laravel/scout": "^9.8", "laravel/telescope": "^4.13", "laravel/tinker": "^2.0", "laravel/ui": "^3.0", diff --git a/composer.lock b/composer.lock index 81c5f76a..ece74438 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b8d71a8e1d598702057d6ecdaeabd3e2", + "content-hash": "f43fe5529405b4aeb18cdac45386f4f6", "packages": [ { "name": "amphp/amp", @@ -1748,24 +1748,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8" + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9" + "phpoption/phpoption": "^1.9.1" }, "require-dev": { - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" }, "type": "library", "autoload": { @@ -1794,7 +1794,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" }, "funding": [ { @@ -1806,7 +1806,7 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:56:11+00:00" + "time": "2023-02-25T20:23:15+00:00" }, { "name": "guzzlehttp/guzzle", @@ -2637,6 +2637,79 @@ }, "time": "2023-02-10T15:40:38+00:00" }, + { + "name": "laravel/scout", + "version": "v9.8.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/scout.git", + "reference": "38595717b396ce733d432b82e3225fa4e0d6c8ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/scout/zipball/38595717b396ce733d432b82e3225fa4e0d6c8ef", + "reference": "38595717b396ce733d432b82e3225fa4e0d6c8ef", + "shasum": "" + }, + "require": { + "illuminate/bus": "^8.0|^9.0|^10.0", + "illuminate/contracts": "^8.0|^9.0|^10.0", + "illuminate/database": "^8.0|^9.0|^10.0", + "illuminate/http": "^8.0|^9.0|^10.0", + "illuminate/pagination": "^8.0|^9.0|^10.0", + "illuminate/queue": "^8.0|^9.0|^10.0", + "illuminate/support": "^8.0|^9.0|^10.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "meilisearch/meilisearch-php": "^0.19", + "mockery/mockery": "^1.0", + "orchestra/testbench": "^6.17|^7.0|^8.0", + "php-http/guzzle7-adapter": "^1.0", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).", + "meilisearch/meilisearch-php": "Required to use the MeiliSearch engine (^0.23)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Scout\\ScoutServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Scout\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Scout provides a driver based solution to searching your Eloquent models.", + "keywords": [ + "algolia", + "laravel", + "search" + ], + "support": { + "issues": "https://github.com/laravel/scout/issues", + "source": "https://github.com/laravel/scout" + }, + "time": "2023-02-14T16:53:14+00:00" + }, { "name": "laravel/serializable-closure", "version": "v1.3.0", @@ -4420,24 +4493,24 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab" + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8", - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" }, "type": "library", "extra": { @@ -4479,7 +4552,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.0" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" }, "funding": [ { @@ -4491,7 +4564,7 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:51:26+00:00" + "time": "2023-02-25T19:38:58+00:00" }, { "name": "psr/cache", @@ -9563,7 +9636,7 @@ "dist": { "type": "path", "url": "./packages/tex", - "reference": "48251272de62e3fea044a7ad31e1a411c15eb4c6" + "reference": "6f162102ef7ceca41822d18c3e694abd926f550b" }, "type": "library", "extra": { @@ -10000,23 +10073,23 @@ }, { "name": "orchestra/testbench", - "version": "v7.22.0", + "version": "v7.22.1", "source": { "type": "git", "url": "https://github.com/orchestral/testbench.git", - "reference": "4e16b1adde9d13aff6e4221c841bb82dcd4416f0" + "reference": "987f5383f597a54f8d9868f98411899398348c02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench/zipball/4e16b1adde9d13aff6e4221c841bb82dcd4416f0", - "reference": "4e16b1adde9d13aff6e4221c841bb82dcd4416f0", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/987f5383f597a54f8d9868f98411899398348c02", + "reference": "987f5383f597a54f8d9868f98411899398348c02", "shasum": "" }, "require": { "fakerphp/faker": "^1.21", - "laravel/framework": "^9.50.2", + "laravel/framework": "^9.52.4", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^7.22", + "orchestra/testbench-core": "^7.22.1", "php": "^8.0", "phpunit/phpunit": "^9.5.10", "spatie/laravel-ray": "^1.28", @@ -10053,22 +10126,22 @@ ], "support": { "issues": "https://github.com/orchestral/testbench/issues", - "source": "https://github.com/orchestral/testbench/tree/v7.22.0" + "source": "https://github.com/orchestral/testbench/tree/v7.22.1" }, - "time": "2023-02-08T02:31:25+00:00" + "time": "2023-02-24T01:07:22+00:00" }, { "name": "orchestra/testbench-core", - "version": "v7.22.0", + "version": "v7.22.1", "source": { "type": "git", "url": "https://github.com/orchestral/testbench-core.git", - "reference": "a6e4240149ffc1bb3068a68fe4261a5b59753079" + "reference": "75b42f9130e7903ffa0ef8dbb466962ca6635261" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/a6e4240149ffc1bb3068a68fe4261a5b59753079", - "reference": "a6e4240149ffc1bb3068a68fe4261a5b59753079", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/75b42f9130e7903ffa0ef8dbb466962ca6635261", + "reference": "75b42f9130e7903ffa0ef8dbb466962ca6635261", "shasum": "" }, "require": { @@ -10076,7 +10149,7 @@ }, "require-dev": { "fakerphp/faker": "^1.21", - "laravel/framework": "^9.50.2", + "laravel/framework": "^9.52.4", "laravel/pint": "^1.4", "mockery/mockery": "^1.5.1", "orchestra/canvas": "^7.0", @@ -10090,7 +10163,7 @@ "suggest": { "brianium/paratest": "Allow using parallel tresting (^6.4).", "fakerphp/faker": "Allow using Faker for testing (^1.21).", - "laravel/framework": "Required for testing (^9.50.2).", + "laravel/framework": "Required for testing (^9.52.4).", "mockery/mockery": "Allow using Mockery for testing (^1.5.1).", "nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^6.2).", "orchestra/testbench-browser-kit": "Allow using legacy Laravel BrowserKit for testing (^7.0).", @@ -10141,7 +10214,7 @@ "issues": "https://github.com/orchestral/testbench/issues", "source": "https://github.com/orchestral/testbench-core" }, - "time": "2023-02-08T02:19:30+00:00" + "time": "2023-02-23T12:15:23+00:00" }, { "name": "phar-io/manifest", @@ -10343,16 +10416,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.1", + "version": "1.10.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "1cd5fc530a8b68702f3733ad64294b2a39564198" + "reference": "5419375b5891add97dc74be71e6c1c34baaddf64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1cd5fc530a8b68702f3733ad64294b2a39564198", - "reference": "1cd5fc530a8b68702f3733ad64294b2a39564198", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5419375b5891add97dc74be71e6c1c34baaddf64", + "reference": "5419375b5891add97dc74be71e6c1c34baaddf64", "shasum": "" }, "require": { @@ -10382,7 +10455,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.10.1" + "source": "https://github.com/phpstan/phpstan/tree/1.10.3" }, "funding": [ { @@ -10398,27 +10471,27 @@ "type": "tidelift" } ], - "time": "2023-02-21T21:57:23+00:00" + "time": "2023-02-25T14:47:13+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.24", + "version": "9.2.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" + "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e2b40518197a8c0d4b08bc34dfff1c99c508954", + "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -10467,7 +10540,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.25" }, "funding": [ { @@ -10475,7 +10548,7 @@ "type": "github" } ], - "time": "2023-01-26T08:26:55+00:00" + "time": "2023-02-25T05:32:00+00:00" }, { "name": "phpunit/php-file-iterator", diff --git a/config/scout.php b/config/scout.php new file mode 100644 index 00000000..52d1d560 --- /dev/null +++ b/config/scout.php @@ -0,0 +1,142 @@ + env('SCOUT_DRIVER', 'database'), + + /* + |-------------------------------------------------------------------------- + | Index Prefix + |-------------------------------------------------------------------------- + | + | Here you may specify a prefix that will be applied to all search index + | names used by Scout. This prefix may be useful if you have multiple + | "tenants" or applications sharing the same search infrastructure. + | + */ + + 'prefix' => env('SCOUT_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Queue Data Syncing + |-------------------------------------------------------------------------- + | + | This option allows you to control if the operations that sync your data + | with your search engines are queued. When this is set to "true" then + | all automatic data syncing will get queued for better performance. + | + */ + + 'queue' => env('SCOUT_QUEUE', false), + + /* + |-------------------------------------------------------------------------- + | Database Transactions + |-------------------------------------------------------------------------- + | + | This configuration option determines if your data will only be synced + | with your search indexes after every open database transaction has + | been committed, thus preventing any discarded data from syncing. + | + */ + + 'after_commit' => false, + + /* + |-------------------------------------------------------------------------- + | Chunk Sizes + |-------------------------------------------------------------------------- + | + | These options allow you to control the maximum chunk size when you are + | mass importing data into the search engine. This allows you to fine + | tune each of these chunk sizes based on the power of the servers. + | + */ + + 'chunk' => [ + 'searchable' => 500, + 'unsearchable' => 500, + ], + + /* + |-------------------------------------------------------------------------- + | Soft Deletes + |-------------------------------------------------------------------------- + | + | This option allows to control whether to keep soft deleted records in + | the search indexes. Maintaining soft deleted records can be useful + | if your application still needs to search for the records later. + | + */ + + 'soft_delete' => false, + + /* + |-------------------------------------------------------------------------- + | Identify User + |-------------------------------------------------------------------------- + | + | This option allows you to control whether to notify the search engine + | of the user performing the search. This is sometimes useful if the + | engine supports any analytics based on this application's users. + | + | Supported engines: "algolia" + | + */ + + 'identify' => env('SCOUT_IDENTIFY', false), + + /* + |-------------------------------------------------------------------------- + | Algolia Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure your Algolia settings. Algolia is a cloud hosted + | search engine which works great with Scout out of the box. Just plug + | in your application ID and admin API key to get started searching. + | + */ + + 'algolia' => [ + 'id' => env('ALGOLIA_APP_ID', ''), + 'secret' => env('ALGOLIA_SECRET', ''), + ], + + /* + |-------------------------------------------------------------------------- + | MeiliSearch Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure your MeiliSearch settings. MeiliSearch is an open + | source search engine with minimal configuration. Below, you can state + | the host and key information for your own MeiliSearch installation. + | + | See: https://docs.meilisearch.com/guides/advanced_guides/configuration.html + | + */ + + 'meilisearch' => [ + 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), + 'key' => env('MEILISEARCH_KEY', null), + 'index-settings' => [ + // 'users' => [ + // 'filterableAttributes'=> ['id', 'name', 'email'], + // ], + ], + ], + +]; diff --git a/database/migrations/2023_02_26_192658_create_members_search_column.php b/database/migrations/2023_02_26_192658_create_members_search_column.php new file mode 100644 index 00000000..1b79b6e9 --- /dev/null +++ b/database/migrations/2023_02_26_192658_create_members_search_column.php @@ -0,0 +1,33 @@ +text('search_text')->default(''); + $table->fullText('search_text'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('members', function (Blueprint $table) { + $table->dropColumn('search_text'); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index eadf1a53..44182411 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,12 +18,14 @@ }, "devDependencies": { "autoprefixer": "latest", + "axios": "^1.3.4", "eslint": "^8.9.0", "eslint-plugin-vue": "^8.4.1", "laravel-mix": "^6.0.1", "postcss": "^8.4.6", "tailwindcss": "^3.0.19", "vue": "^2.6", + "vue-axios": "^3.5.2", "vue-loader": "^15.9.8", "vue-template-compiler": "^2.6.14" } @@ -1836,6 +1838,14 @@ "vue": "^2.6.0" } }, + "node_modules/@inertiajs/inertia/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.4.tgz", @@ -2955,6 +2965,12 @@ "lodash": "^4.17.14" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "node_modules/autoprefixer": { "version": "10.4.2", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", @@ -2983,11 +2999,14 @@ } }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "dev": true, "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/babel-loader": { @@ -3724,6 +3743,18 @@ "text-hex": "1.0.x" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -4403,6 +4434,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -5428,9 +5468,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "node_modules/follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "funding": [ { "type": "individual", @@ -5446,6 +5486,20 @@ } } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -8494,6 +8548,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -10134,6 +10194,16 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==" }, + "node_modules/vue-axios": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-3.5.2.tgz", + "integrity": "sha512-GP+dct7UlAWkl1qoP3ppw0z6jcSua5/IrMpjB5O8bh089iIiJ+hdxPYH2NPEpajlYgkW5EVMP95ttXWdas1O0g==", + "dev": true, + "peerDependencies": { + "axios": "*", + "vue": "^3.0.0 || ^2.0.0" + } + }, "node_modules/vue-eslint-parser": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.2.0.tgz", @@ -12170,6 +12240,16 @@ "axios": "^0.21.1", "deepmerge": "^4.0.0", "qs": "^6.9.0" + }, + "dependencies": { + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + } } }, "@inertiajs/inertia-vue": { @@ -13091,6 +13171,12 @@ "lodash": "^4.17.14" } }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "autoprefixer": { "version": "10.4.2", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz", @@ -13106,11 +13192,14 @@ } }, "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "dev": true, "requires": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "babel-loader": { @@ -13705,6 +13794,15 @@ "text-hex": "1.0.x" } }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -14229,6 +14327,12 @@ } } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -15037,9 +15141,20 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } }, "forwarded": { "version": "0.2.0", @@ -17240,6 +17355,12 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -18514,6 +18635,13 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==" }, + "vue-axios": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-3.5.2.tgz", + "integrity": "sha512-GP+dct7UlAWkl1qoP3ppw0z6jcSua5/IrMpjB5O8bh089iIiJ+hdxPYH2NPEpajlYgkW5EVMP95ttXWdas1O0g==", + "dev": true, + "requires": {} + }, "vue-eslint-parser": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.2.0.tgz", diff --git a/package.json b/package.json index 47f30215..fd3cf5f9 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ }, "devDependencies": { "autoprefixer": "latest", + "axios": "^1.3.4", "eslint": "^8.9.0", "eslint-plugin-vue": "^8.4.1", "laravel-mix": "^6.0.1", "postcss": "^8.4.6", "tailwindcss": "^3.0.19", "vue": "^2.6", + "vue-axios": "^3.5.2", "vue-loader": "^15.9.8", "vue-template-compiler": "^2.6.14" }, diff --git a/packages/laravel-nami b/packages/laravel-nami index 315be84c..7b08f30c 160000 --- a/packages/laravel-nami +++ b/packages/laravel-nami @@ -1 +1 @@ -Subproject commit 315be84cf38f1a01f890180099d3b2156bf344b9 +Subproject commit 7b08f30cb2d44a74f59c1441f797ab544e753701 diff --git a/resources/js/app.js b/resources/js/app.js index 704d5e8b..a38dc1db 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -12,10 +12,13 @@ import AppLayout from './layouts/AppLayout.vue'; import VTooltip from 'v-tooltip'; import hasModule from './mixins/hasModule.js'; import PortalVue from 'portal-vue'; +import axios from 'axios'; +import VueAxios from 'vue-axios'; Vue.use(plugin); Vue.use(PortalVue); Vue.use(VTooltip); +Vue.use(VueAxios, axios); Vue.component('f-text', () => import(/* webpackChunkName: "form" */ './components/FText')); Vue.component('f-switch', () => import(/* webpackChunkName: "form" */ './components/FSwitch')); Vue.component('f-select', () => import(/* webpackChunkName: "form" */ './components/FSelect')); diff --git a/resources/js/views/contribution/VIndex.vue b/resources/js/views/contribution/VIndex.vue index 635aa07b..2bc36da4 100644 --- a/resources/js/views/contribution/VIndex.vue +++ b/resources/js/views/contribution/VIndex.vue @@ -1,5 +1,5 @@ diff --git a/routes/web.php b/routes/web.php index afc2c391..506b9856 100644 --- a/routes/web.php +++ b/routes/web.php @@ -15,6 +15,7 @@ use App\Initialize\Actions\InitializeAction; use App\Initialize\Actions\InitializeFormAction; use App\Member\Actions\MemberResyncAction; use App\Member\Actions\MemberShowAction; +use App\Member\Actions\SearchAction; use App\Member\MemberController; use App\Membership\Actions\MembershipDestroyAction; use App\Membership\Actions\MembershipStoreAction; @@ -32,6 +33,7 @@ Route::group(['namespace' => 'App\\Http\\Controllers'], function (): void { Route::group(['middleware' => 'auth:web'], function (): void { Route::get('/', HomeIndexAction::class)->name('home'); + Route::post('/api/member/search', SearchAction::class)->name('member.search'); Route::get('/initialize', InitializeFormAction::class)->name('initialize.form'); Route::post('/initialize', InitializeAction::class)->name('initialize.store'); Route::resource('member', MemberController::class)->except('show'); diff --git a/tests/Feature/Contribution/IndexTest.php b/tests/Feature/Contribution/IndexTest.php index e8cad725..26d90f9d 100644 --- a/tests/Feature/Contribution/IndexTest.php +++ b/tests/Feature/Contribution/IndexTest.php @@ -15,16 +15,22 @@ class IndexTest extends TestCase public function testItHasContributionIndex(): void { $this->withoutExceptionHandling()->login()->loginNami(); - Country::factory()->create(['name' => 'Deutschland']); + $country = Country::factory()->create(['name' => 'Deutschland']); Member::factory()->defaults()->create(['firstname' => 'Max', 'lastname' => 'Muster']); Member::factory()->defaults()->create(['firstname' => 'Jane', 'lastname' => 'Muster']); $response = $this->get('/contribution'); - $this->assertInertiaHas('Jane', $response, 'allMembers.data.0.firstname'); $this->assertInertiaHas([ 'class' => DvDocument::class, 'title' => 'Für DV erstellen', ], $response, 'compilers.0'); + $this->assertInertiaHas([ + 'id' => $country->id, + 'name' => $country->name, + ], $response, 'countries.0'); + $this->assertInertiaHas([ + 'country' => $country->id, + ], $response, 'data'); } } diff --git a/tests/Feature/Member/SearchTest.php b/tests/Feature/Member/SearchTest.php new file mode 100644 index 00000000..db17f8e1 --- /dev/null +++ b/tests/Feature/Member/SearchTest.php @@ -0,0 +1,28 @@ +withoutExceptionHandling()->login(); + + $member = Member::factory()->defaults()->create([ + 'firstname' => '::firstname::', + 'lastname' => '::lastname::', + 'address' => 'Kölner Str 3', + 'zip' => 33333, + 'location' => 'Hilden', + ]); + + $this->assertEquals('::firstname:: ::lastname:: Kölner Str 3, 33333 Hilden', $member->search_text); + } + +}