Add meilisearch
continuous-integration/drone/push Build is failing Details

This commit is contained in:
philipp lang 2024-01-25 23:40:29 +01:00
parent 5e0dd8df62
commit f204f10b21
11 changed files with 3238 additions and 3927 deletions

View File

@ -287,8 +287,6 @@ class Member extends Model implements Geolocatable
$position->delete(); $position->delete();
}); });
}); });
static::saving(fn ($model) => $model->updateSearch());
} }
// ---------------------------------- Scopes ----------------------------------- // ---------------------------------- Scopes -----------------------------------
@ -312,7 +310,7 @@ class Member extends Model implements Geolocatable
return $query->addSelect([ return $query->addSelect([
'pending_payment' => InvoicePosition::selectRaw('SUM(price)') 'pending_payment' => InvoicePosition::selectRaw('SUM(price)')
->whereColumn('invoice_positions.member_id', 'members.id') ->whereColumn('invoice_positions.member_id', 'members.id')
->whereHas('invoice', fn ($query) => $query->whereNeedsPayment()) ->whereHas('invoice', fn ($query) => $query->whereNeedsPayment()),
]); ]);
} }
@ -466,25 +464,6 @@ class Member extends Model implements Geolocatable
]); ]);
} }
/**
* @return array<string, mixed>
*/
#[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(' ');
}
/** /**
* @return array<int, array{id: int, name: string}> * @return array<int, array{id: int, name: string}>
*/ */
@ -519,4 +498,20 @@ class Member extends Model implements Geolocatable
|| $this->getOriginal('zip') !== $this->zip || $this->getOriginal('zip') !== $this->zip
|| $this->getOriginal('location') !== $this->location; || $this->getOriginal('location') !== $this->location;
} }
// --------------------------------- Searching ---------------------------------
// *****************************************************************************
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray()
{
return [
'address' => $this->fullAddress,
'fullname' => $this->fullname,
];
}
} }

View File

@ -40,6 +40,7 @@
"laravel/ui": "^3.0", "laravel/ui": "^3.0",
"league/csv": "^9.9", "league/csv": "^9.9",
"lorisleiva/laravel-actions": "^2.4", "lorisleiva/laravel-actions": "^2.4",
"meilisearch/meilisearch-php": "^1.6",
"monicahq/laravel-sabre": "^1.6", "monicahq/laravel-sabre": "^1.6",
"nunomaduro/collision": "^6.1", "nunomaduro/collision": "^6.1",
"phake/phake": "^4.2", "phake/phake": "^4.2",
@ -69,7 +70,8 @@
"preferred-install": "dist", "preferred-install": "dist",
"sort-packages": true, "sort-packages": true,
"allow-plugins": { "allow-plugins": {
"pestphp/pest-plugin": true "pestphp/pest-plugin": true,
"php-http/discovery": true
} }
}, },
"extra": { "extra": {

2690
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
<?php <?php
use App\Member\Member;
return [ return [
/* /*
@ -15,7 +17,7 @@ return [
| |
*/ */
'driver' => env('SCOUT_DRIVER', 'database'), 'driver' => env('SCOUT_DRIVER', 'manticore'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -133,9 +135,9 @@ return [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY', null), 'key' => env('MEILISEARCH_KEY', null),
'index-settings' => [ 'index-settings' => [
// 'users' => [ Member::class => [
// 'filterableAttributes'=> ['id', 'name', 'email'], 'filterableAttributes' => ['fullname', 'address'],
// ], ]
], ],
], ],

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('members', function (Blueprint $table) {
$table->dropColumn('search_text');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('members', function (Blueprint $table) {
$table->text('search_text')->default('');
$table->fullText('search_text');
});
}
};

View File

@ -100,3 +100,11 @@ services:
restart: always restart: always
volumes: volumes:
- ./data/redis:/data - ./data/redis:/data
meilisearch:
image: getmeili/meilisearch:v1.6
command: 'meilisearch --master-key="abc"'
volumes:
- ./data/meilisearch:/meili_data
ports:
- '7700:7700'

View File

@ -101,3 +101,11 @@ services:
image: redis:alpine3.18 image: redis:alpine3.18
volumes: volumes:
- ./data/redis:/data - ./data/redis:/data
meilisearch:
image: getmeili/meilisearch:v1.6
command: 'meilisearch --master-key="abc"'
volumes:
- ./data/meilisearch:/meili_data
ports:
- '7700:7700'

4287
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,33 +12,32 @@
"fix": "eslint \"resources/js/**/*.{js,vue}\" --fix" "fix": "eslint \"resources/js/**/*.{js,vue}\" --fix"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.17",
"axios": "^1.4.0", "axios": "^1.6.6",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"eslint": "^8.43.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.10.0",
"eslint-plugin-vue": "^8.7.1", "eslint-plugin-vue": "^8.7.1",
"postcss": "^8.4.24", "postcss": "^8.4.33",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.4.1",
"typescript": "^5.1.6",
"vue-axios": "^3.5.2" "vue-axios": "^3.5.2"
}, },
"dependencies": { "dependencies": {
"@inertiajs/vue3": "^1.0.8", "@inertiajs/vue3": "^1.0.14",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.10",
"@vitejs/plugin-vue": "^4.2.3", "@vitejs/plugin-vue": "^4.6.2",
"change-case": "^4.1.2", "change-case": "^4.1.2",
"floating-vue": "^2.0.0-beta.24", "floating-vue": "^2.0.0",
"laravel-echo": "^1.15.2", "laravel-echo": "^1.15.3",
"laravel-vite-plugin": "^0.7.8", "laravel-vite-plugin": "^0.7.8",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merge": "^2.1.1", "merge": "^2.1.1",
"pinia": "^2.1.4", "pinia": "^2.1.7",
"postcss-import": "^14.1.0", "postcss-import": "^14.1.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"pusher-js": "^8.3.0", "pusher-js": "^8.3.0",
"svg-sprite": "^2.0.2", "svg-sprite": "^2.0.2",
"vite": "^4.3.9", "vite": "^4.5.2",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-toastification": "^2.0.0-rc.5", "vue-toastification": "^2.0.0-rc.5",
"wnumb": "^1.2.0" "wnumb": "^1.2.0"

View File

@ -9,15 +9,16 @@ export function useIndex(props, siteName) {
const inner = { const inner = {
data: ref(rawProps.data), data: ref(rawProps.data),
meta: ref(rawProps.meta), meta: ref(rawProps.meta),
filter: ref(rawProps.meta.filter ? rawProps.meta.filter : {}),
}; };
function toFilterString(data) { function toFilterString(data) {
return btoa(encodeURIComponent(JSON.stringify(data))); return btoa(encodeURIComponent(JSON.stringify(data)));
} }
const filterString = computed(() => toFilterString(inner.meta.value.filter)); const filterString = computed(() => toFilterString(inner.filter.value));
function reload(resetPage = true, withMeta = true, data) { function reload(resetPage = true, data) {
var data = { var data = {
filter: filterString.value, filter: filterString.value,
page: resetPage ? 1 : inner.meta.value.current_page, page: resetPage ? 1 : inner.meta.value.current_page,
@ -30,18 +31,16 @@ export function useIndex(props, siteName) {
only: ['data'], only: ['data'],
onSuccess: (page) => { onSuccess: (page) => {
inner.data.value = page.props.data.data; inner.data.value = page.props.data.data;
if (withMeta) { inner.meta.value = {
inner.meta.value = { ...inner.meta.value,
...inner.meta.value, ...page.props.data.meta,
...page.props.data.meta, };
};
}
}, },
}); });
} }
function reloadPage(page) { function reloadPage(page) {
reload(false, true, {page: page}); reload(false, {page: page});
} }
function can(permission) { function can(permission) {
@ -49,12 +48,12 @@ export function useIndex(props, siteName) {
} }
function getFilter(value) { function getFilter(value) {
return inner.meta.value.filter[value]; return inner.filter.value[value];
} }
function setFilter(key, value) { function setFilter(key, value) {
inner.meta.value.filter[key] = value; inner.filter.value[key] = value;
reload(true, false); reload(true);
} }
startListener(); startListener();
@ -62,7 +61,6 @@ export function useIndex(props, siteName) {
return { return {
data: inner.data, data: inner.data,
reload,
can, can,
getFilter, getFilter,
setFilter, setFilter,

View File

@ -1,31 +1,29 @@
<template> <template>
<page-layout> <page-layout>
<template #toolbar> <template #toolbar>
<page-toolbar-button color="primary" icon="plus" @click.prevent="model = { ...meta.default }">Neue <page-toolbar-button color="primary" icon="plus" @click.prevent="create">Neue Verbindung</page-toolbar-button>
Verbindung</page-toolbar-button>
</template> </template>
<ui-popup v-if="model !== null" :heading="model.id ? 'Verbindung bearbeiten' : 'Neue Verbindung'" <ui-popup v-if="single !== null" :heading="single.id ? 'Verbindung bearbeiten' : 'Neue Verbindung'" @close="cancel">
@close="model = null">
<form @submit.prevent="submit"> <form @submit.prevent="submit">
<section class="grid grid-cols-2 gap-3 mt-6"> <section class="grid grid-cols-2 gap-3 mt-6">
<f-text id="name" v-model="model.name" name="name" label="Bezeichnung" required></f-text> <f-text id="name" v-model="single.name" name="name" label="Bezeichnung" required></f-text>
<f-text id="domain" v-model="model.domain" name="domain" label="Domain" required></f-text> <f-text id="domain" v-model="single.domain" name="domain" label="Domain" required></f-text>
<f-select id="type" :model-value="model.type.cls" label="Typ" name="type" :options="meta.types" <f-select id="type" :model-value="single.type.cls" label="Typ" name="type" :options="meta.types"
:placeholder="''" required @update:model-value=" :placeholder="''" required @update:model-value="
model.type = { single.type = {
cls: $event, cls: $event,
params: { ...getType($event).defaults }, params: { ...getType($event).defaults },
} }
"></f-select> "></f-select>
<template v-for="(field, index) in getType(model.type.cls).fields"> <template v-for="(field, index) in getType(single.type.cls).fields">
<f-text v-if="field.type === 'text' || field.type === 'password' || field.type === 'email'" <f-text v-if="field.type === 'text' || field.type === 'password' || field.type === 'email'"
:id="field.name" :key="index" v-model="model.type.params[field.name]" :label="field.label" :id="field.name" :key="index" v-model="single.type.params[field.name]" :label="field.label"
:type="field.type" :name="field.name" :required="field.is_required"></f-text> :type="field.type" :name="field.name" :required="field.is_required"></f-text>
</template> </template>
</section> </section>
<section class="flex mt-4 space-x-2"> <section class="flex mt-4 space-x-2">
<ui-button type="submit" class="btn-danger">Speichern</ui-button> <ui-button type="submit" class="btn-danger">Speichern</ui-button>
<ui-button class="btn-primary" @click.prevent="model = null">Abbrechen</ui-button> <ui-button class="btn-primary" @click.prevent="single = null">Abbrechen</ui-button>
</section> </section>
</form> </form>
</ui-popup> </ui-popup>
@ -50,7 +48,7 @@
</td> </td>
<td> <td>
<a v-tooltip="`Bearbeiten`" href="#" class="inline-flex btn btn-warning btn-sm" <a v-tooltip="`Bearbeiten`" href="#" class="inline-flex btn btn-warning btn-sm"
@click.prevent="model = { ...gateway }"><ui-sprite src="pencil"></ui-sprite></a> @click.prevent="edit(gateway)"><ui-sprite src="pencil"></ui-sprite></a>
</td> </td>
</tr> </tr>
</table> </table>
@ -64,22 +62,13 @@
</template> </template>
<script setup> <script setup>
import { ref, inject } from 'vue'; import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
import { indexProps, useIndex } from '../../composables/useIndex.js';
import SettingLayout from '../setting/Layout.vue'; import SettingLayout from '../setting/Layout.vue';
const props = defineProps(indexProps); const props = defineProps(indexProps);
const { meta, data, reload } = useIndex(props.data, 'mailgateway'); const { meta, data, create, edit, cancel, single, submit } = useIndex(props.data, 'mailgateway');
const model = ref(null);
const axios = inject('axios');
function getType(type) { function getType(type) {
return meta.value.types.find((t) => t.id === type); return meta.value.types.find((t) => t.id === type);
} }
async function submit() {
await axios[model.value.id ? 'patch' : 'post'](model.value.id ? model.value.links.update : meta.value.links.store, model.value);
reload();
model.value = null;
}
</script> </script>