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();
});
});
static::saving(fn ($model) => $model->updateSearch());
}
// ---------------------------------- Scopes -----------------------------------
@ -312,7 +310,7 @@ class Member extends Model implements Geolocatable
return $query->addSelect([
'pending_payment' => InvoicePosition::selectRaw('SUM(price)')
->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}>
*/
@ -519,4 +498,20 @@ class Member extends Model implements Geolocatable
|| $this->getOriginal('zip') !== $this->zip
|| $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",
"league/csv": "^9.9",
"lorisleiva/laravel-actions": "^2.4",
"meilisearch/meilisearch-php": "^1.6",
"monicahq/laravel-sabre": "^1.6",
"nunomaduro/collision": "^6.1",
"phake/phake": "^4.2",
@ -69,7 +70,8 @@
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"extra": {

2690
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
<?php
use App\Member\Member;
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'),
'key' => env('MEILISEARCH_KEY', null),
'index-settings' => [
// 'users' => [
// 'filterableAttributes'=> ['id', 'name', 'email'],
// ],
Member::class => [
'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
volumes:
- ./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
volumes:
- ./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"
},
"devDependencies": {
"autoprefixer": "^10.4.14",
"axios": "^1.4.0",
"autoprefixer": "^10.4.17",
"axios": "^1.6.6",
"dayjs": "^1.11.10",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-vue": "^8.7.1",
"postcss": "^8.4.24",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.6",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",
"vue-axios": "^3.5.2"
},
"dependencies": {
"@inertiajs/vue3": "^1.0.8",
"@tailwindcss/typography": "^0.5.9",
"@vitejs/plugin-vue": "^4.2.3",
"@inertiajs/vue3": "^1.0.14",
"@tailwindcss/typography": "^0.5.10",
"@vitejs/plugin-vue": "^4.6.2",
"change-case": "^4.1.2",
"floating-vue": "^2.0.0-beta.24",
"laravel-echo": "^1.15.2",
"floating-vue": "^2.0.0",
"laravel-echo": "^1.15.3",
"laravel-vite-plugin": "^0.7.8",
"lodash": "^4.17.21",
"merge": "^2.1.1",
"pinia": "^2.1.4",
"pinia": "^2.1.7",
"postcss-import": "^14.1.0",
"prettier": "^2.8.8",
"pusher-js": "^8.3.0",
"svg-sprite": "^2.0.2",
"vite": "^4.3.9",
"vite": "^4.5.2",
"vue": "^3.3.4",
"vue-toastification": "^2.0.0-rc.5",
"wnumb": "^1.2.0"

View File

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

View File

@ -1,31 +1,29 @@
<template>
<page-layout>
<template #toolbar>
<page-toolbar-button color="primary" icon="plus" @click.prevent="model = { ...meta.default }">Neue
Verbindung</page-toolbar-button>
<page-toolbar-button color="primary" icon="plus" @click.prevent="create">Neue Verbindung</page-toolbar-button>
</template>
<ui-popup v-if="model !== null" :heading="model.id ? 'Verbindung bearbeiten' : 'Neue Verbindung'"
@close="model = null">
<ui-popup v-if="single !== null" :heading="single.id ? 'Verbindung bearbeiten' : 'Neue Verbindung'" @close="cancel">
<form @submit.prevent="submit">
<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="domain" v-model="model.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-text id="name" v-model="single.name" name="name" label="Bezeichnung" required></f-text>
<f-text id="domain" v-model="single.domain" name="domain" label="Domain" required></f-text>
<f-select id="type" :model-value="single.type.cls" label="Typ" name="type" :options="meta.types"
:placeholder="''" required @update:model-value="
model.type = {
single.type = {
cls: $event,
params: { ...getType($event).defaults },
}
"></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'"
: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>
</template>
</section>
<section class="flex mt-4 space-x-2">
<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>
</form>
</ui-popup>
@ -50,7 +48,7 @@
</td>
<td>
<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>
</tr>
</table>
@ -64,22 +62,13 @@
</template>
<script setup>
import { ref, inject } from 'vue';
import { indexProps, useIndex } from '../../composables/useIndex.js';
import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
import SettingLayout from '../setting/Layout.vue';
const props = defineProps(indexProps);
const { meta, data, reload } = useIndex(props.data, 'mailgateway');
const model = ref(null);
const axios = inject('axios');
const { meta, data, create, edit, cancel, single, submit } = useIndex(props.data, 'mailgateway');
function getType(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>