Add member search via meilisearch

This commit is contained in:
philipp lang 2024-01-28 11:42:32 +01:00
parent 26d0f49568
commit 4e378c8a57
29 changed files with 1218 additions and 394 deletions

View File

@ -16,6 +16,8 @@ MAIL_FROM_NAME=me
DB_PASSWORD=secret_db_password DB_PASSWORD=secret_db_password
MYSQL_PASSWORD=secret_db_password MYSQL_PASSWORD=secret_db_password
MEILI_MASTER_KEY=secret_meilisearch_password
PUSHER_APP_HOST=socketi PUSHER_APP_HOST=socketi
WORKERS=5 WORKERS=5

View File

@ -5,6 +5,7 @@ namespace App\Http\Views;
use App\Activity; use App\Activity;
use App\Lib\Filter; use App\Lib\Filter;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Laravel\Scout\Builder as ScoutBuilder;
use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Mappers\SnakeCaseMapper; use Spatie\LaravelData\Mappers\SnakeCaseMapper;
@ -21,12 +22,9 @@ class ActivityFilterScope extends Filter
) { ) {
} }
/** public function getQuery(): ScoutBuilder
* {@inheritdoc}
*/
public function locks(): array
{ {
return []; return Activity::search('');
} }
/** /**

View File

@ -31,10 +31,14 @@ class InvoicePosition extends Model
public static function booted(): void public static function booted(): void
{ {
static::saved(function ($model) {
$model->member->touch();
});
static::deleted(function ($model) { static::deleted(function ($model) {
if ($model->invoice->positions()->get()->count() === 0) { if ($model->invoice->positions()->get()->count() === 0) {
$model->invoice->delete(); $model->invoice->delete();
} }
$model->member->touch();
}); });
} }
} }

View File

@ -2,21 +2,22 @@
namespace App\Lib; namespace App\Lib;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Builder;
use Spatie\LaravelData\Data; use Spatie\LaravelData\Data;
/** /**
* @template T of Model * @template T of Model
* @property Builder $query
*/ */
abstract class Filter extends Data abstract class Filter extends Data
{ {
public string $unsetReplacer = 'yoNee3ainge4eetiier9ogaiChoe0ahcaR3Hu1uzah8xaiv7ael7yahphai7ruG9';
/** /**
* @return array<string, mixed> * @return self<T>
*/ */
abstract protected function locks(): array; abstract public function getQuery(): Builder;
protected Builder $query;
/** /**
* @param array<string, mixed>|string|null $request * @param array<string, mixed>|string|null $request
@ -35,51 +36,6 @@ abstract class Filter extends Data
*/ */
public static function fromPost(?array $post = null): static public static function fromPost(?array $post = null): static
{ {
return static::withoutMagicalCreationFrom($post ?: [])->parseLocks(); return static::withoutMagicalCreationFrom($post ?: []);
}
public function parseLocks(): static
{
foreach ($this->locks() as $key => $value) {
if ($value === $this->unsetReplacer) {
continue;
}
$this->{$key} = $value;
}
return $this;
}
/**
* @param mixed $value
*
* @return mixed
*/
public function when(bool $when, $value)
{
return $when ? $value : $this->unsetReplacer;
}
/**
* @param Builder<T> $query
*
* @return Builder<T>
*/
protected function applyOwnOthers(Builder $query, bool $own, bool $others): Builder
{
if ($own && !$others) {
$query->where('user_id', auth()->id());
}
if (!$own && $others) {
$query->where('user_id', '!=', auth()->id());
}
if (!$own && !$others) {
$query->where('id', -1);
}
return $query;
} }
} }

View File

@ -25,9 +25,7 @@ class ResyncAction
*/ */
public function getResults(Maildispatcher $dispatcher): Collection public function getResults(Maildispatcher $dispatcher): Collection
{ {
return Member::search(data_get($dispatcher->filter, 'search', ''))->query( return FilterScope::fromPost($dispatcher->filter)->getQuery()->get()
fn ($q) => $q->select('*')->withFilter(FilterScope::fromPost($dispatcher->filter))
)->get()
->filter(fn ($member) => $member->email || $member->email_parents) ->filter(fn ($member) => $member->email || $member->email_parents)
->map(fn ($member) => MailEntry::from(['email' => $member->email ?: $member->email_parents])) ->map(fn ($member) => MailEntry::from(['email' => $member->email ?: $member->email_parents]))
->unique(fn ($member) => $member->email); ->unique(fn ($member) => $member->email);

View File

@ -43,9 +43,8 @@ class ExportAction
public function asController(ActionRequest $request): StreamedResponse public function asController(ActionRequest $request): StreamedResponse
{ {
$filter = FilterScope::fromRequest($request->input('filter')); $members = FilterScope::fromRequest($request->input('filter'))->getQuery()->get();
$contents = $this->handle($members);
$contents = $this->handle(Member::ordered()->withFilter($filter)->get());
Storage::disk('temp')->put('mitglieder.csv', $contents); Storage::disk('temp')->put('mitglieder.csv', $contents);

View File

@ -15,19 +15,16 @@ class SearchAction
use AsAction; use AsAction;
/** /**
* @param array<string, mixed> $filter
* @return LengthAwarePaginator<int, Member> * @return LengthAwarePaginator<int, Member>
*/ */
public function handle(FilterScope $filter, int $perPage): LengthAwarePaginator public function handle(array $filter, int $perPage): LengthAwarePaginator
{ {
return Member::search($filter->search)->query( return FilterScope::fromPost($filter)->getQuery()->paginate($perPage);
fn ($q) => $q->select('*')
->withFilter($filter)
->ordered()
)->paginate($perPage);
} }
public function asController(ActionRequest $request): AnonymousResourceCollection public function asController(ActionRequest $request): AnonymousResourceCollection
{ {
return MemberResource::collection($this->handle(FilterScope::fromRequest($request->input('filter', '')), $request->input('per_page', 15))); return MemberResource::collection($this->handle($request->input('filter', []), $request->input('per_page', 15)));
} }
} }

View File

@ -4,7 +4,8 @@ namespace App\Member;
use App\Invoice\BillKind; use App\Invoice\BillKind;
use App\Lib\Filter; use App\Lib\Filter;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection;
use Laravel\Scout\Builder;
use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Mappers\SnakeCaseMapper; use Spatie\LaravelData\Mappers\SnakeCaseMapper;
@ -37,72 +38,93 @@ class FilterScope extends Filter
) { ) {
} }
/** public function getQuery(): Builder
* {@inheritdoc}
*/
public function locks(): array
{ {
return []; $this->search = $this->search ?: '';
}
/** return Member::search($this->search, function ($engine, string $query, array $options) {
* @param Builder<Member> $query $filter = collect([]);
*
* @return Builder<Member>
*/
public function apply(Builder $query): Builder
{
return $query->where(function ($query) {
$query->orWhere(function ($query) {
if ($this->ausstand) {
$query->whereAusstand();
}
if ($this->hasFullAddress === true) {
$filter->push('address IS NOT EMPTY');
}
if ($this->hasFullAddress === false) {
$filter->push('address IS EMPTY');
}
if ($this->hasBirthday === false) {
$filter->push('birthday IS NULL');
}
if ($this->hasBirthday === true) {
$filter->push('birthday IS NOT NULL');
}
if ($this->ausstand === true) {
$filter->push('ausstand > 0');
}
if ($this->billKind) { if ($this->billKind) {
$query->where('bill_kind', BillKind::fromValue($this->billKind)); $filter->push('bill_kind = ' . BillKind::fromValue($this->billKind)->value);
} }
if (true === $this->hasFullAddress) {
$query->whereNotNull('address')->whereNotNull('zip')->whereNotNull('location')->where('address', '!=', '')->where('zip', '!=', '')->where('location', '!=', '');
}
if (false === $this->hasFullAddress) {
$query->where(
fn ($q) => $q
->orWhere('address', '')->orWhereNull('address')
->orWhere('zip', '')->orWhereNull('zip')
->orWhere('location', '')->orWhereNull('location')
);
}
if (true === $this->hasBirthday) {
$query->whereNotNull('birthday');
}
if (count($this->groupIds)) { if (count($this->groupIds)) {
$query->whereIn('group_id', $this->groupIds); $filter->push($this->inExpression('group_id', $this->groupIds));
} }
if (!$this->subactivityIds && $this->activityIds) {
if (count($this->subactivityIds) + count($this->activityIds) > 0) { $filter->push($this->inExpression('memberships.activity_id', $this->activityIds));
$query->whereHas('memberships', function ($q) {
$q->active();
if (count($this->subactivityIds)) {
$q->whereIn('subactivity_id', $this->subactivityIds);
} }
if (count($this->activityIds)) { if ($this->subactivityIds && !$this->activityIds) {
$q->whereIn('activity_id', $this->activityIds); $filter->push($this->inExpression('memberships.subactivity_id', $this->subactivityIds));
} }
}); if ($this->subactivityIds && $this->activityIds) {
$combinations = collect($this->activityIds)
->map(fn ($activityId) => collect($this->subactivityIds)->map(fn ($subactivityId) => $activityId . '|' . $subactivityId))
->flatten()
->map(fn ($combination) => str($combination)->wrap('"'));
$filter->push($this->inExpression('memberships.both', $combinations));
} }
if (count($this->exclude)) { if (count($this->exclude)) {
$query->whereNotIn('id', $this->exclude); $filter->push($this->notInExpression('id', $this->exclude));
} }
})->orWhere(function ($query) {
if (count($this->include)) { $andFilter = $filter->map(fn ($expression) => "($expression)")->implode(' AND ');
$query->whereIn('id', $this->include);
} $options['filter'] = $this->implode(collect([$andFilter])->push($this->inExpression('id', $this->include)), 'OR');
}); $options['sort'] = ['lastname:asc', 'firstname:asc'];
return $engine->search($query, $options);
}); });
} }
/**
* @param Collection<int, mixed> $values
*/
protected function implode(Collection $values, string $between): string
{
return $values->filter(fn ($expression) => $expression)->implode(" {$between} ");
}
/**
* @param array<int, mixed>|Collection<int, mixed> $values
*/
private function inExpression(string $key, array|Collection $values): ?string
{
if (!count($values)) {
return null;
}
$valueString = Collection::wrap($values)->implode(',');
return "$key IN [{$valueString}]";
}
/**
* @param array<int, mixed>|Collection<int, mixed> $values
*/
private function notInExpression(string $key, array|Collection $values): ?string
{
if (!count($values)) {
return null;
}
$valueString = Collection::wrap($values)->implode(',');
return "$key NOT IN [{$valueString}]";
}
} }

View File

@ -149,7 +149,9 @@ class Member extends Model implements Geolocatable
public function getFullAddressAttribute(): string public function getFullAddressAttribute(): string
{ {
return $this->address . ', ' . $this->zip . ' ' . $this->location; return $this->address && $this->zip && $this->location
? $this->address . ', ' . $this->zip . ' ' . $this->location
: '';
} }
public function getEfzLink(): ?string public function getEfzLink(): ?string
@ -178,6 +180,11 @@ class Member extends Model implements Geolocatable
return $this->birthday?->diffInYears(now()); return $this->birthday?->diffInYears(now());
} }
protected function getAusstand(): int
{
return (int) $this->invoicePositions()->whereHas('invoice', fn ($query) => $query->whereNeedsPayment())->sum('price');
}
// ---------------------------------- Relations ---------------------------------- // ---------------------------------- Relations ----------------------------------
/** /**
* @return BelongsTo<Country, self> * @return BelongsTo<Country, self>
@ -324,18 +331,6 @@ class Member extends Model implements Geolocatable
return $query->whereHas('invoicePositions', fn ($q) => $q->whereHas('invoice', fn ($q) => $q->whereNeedsPayment())); return $query->whereHas('invoicePositions', fn ($q) => $q->whereHas('invoice', fn ($q) => $q->whereNeedsPayment()));
} }
/**
* @param Builder<self> $query
*
* @return Builder<self>
*/
public function scopeWhereAusstand(Builder $query): Builder
{
return $query->whereHas('invoicePositions', function ($q) {
return $q->whereHas('invoice', fn ($query) => $query->whereNeedsPayment());
});
}
/** /**
* @param Builder<self> $query * @param Builder<self> $query
* *
@ -356,18 +351,6 @@ class Member extends Model implements Geolocatable
return $query->selectRaw('SUM(id)'); return $query->selectRaw('SUM(id)');
} }
/**
* @todo refactor this to an actual filter model
*
* @param Builder<self> $query
*
* @return Builder<self>
*/
public function scopeWithFilter(Builder $query, FilterScope $filter): Builder
{
return $filter->apply($query);
}
/** /**
* @param Builder<self> $query * @param Builder<self> $query
* *
@ -512,6 +495,14 @@ class Member extends Model implements Geolocatable
return [ return [
'address' => $this->fullAddress, 'address' => $this->fullAddress,
'fullname' => $this->fullname, 'fullname' => $this->fullname,
'firstname' => $this->firstname,
'lastname' => $this->lastname,
'birthday' => $this->birthday?->format('Y-m-d'),
'ausstand' => $this->getAusstand(),
'bill_kind' => $this->bill_kind?->value,
'group_id' => $this->group->id,
'memberships' => $this->memberships()->active()->get()
->map(fn ($membership) => [...$membership->only('activity_id', 'subactivity_id'), 'both' => $membership->activity_id . '|' . $membership->subactivity_id]),
]; ];
} }
} }

View File

@ -17,15 +17,13 @@ class MemberController extends Controller
{ {
session()->put('menu', 'member'); session()->put('menu', 'member');
session()->put('title', 'Mitglieder'); session()->put('title', 'Mitglieder');
$filter = FilterScope::fromRequest($request->input('filter', ''));
return Inertia::render('member/VIndex', [ return Inertia::render('member/VIndex', [
'data' => MemberResource::collection(Member::search($filter->search)->query( 'data' => MemberResource::collection(FilterScope::fromRequest($request->input('filter', ''))->getQuery()->query(
fn ($q) => $q->select('*') fn ($q) => $q
->withFilter($filter) ->select('*')
->with(['gender', 'subscription', 'leaderMemberships', 'ageGroupMemberships.subactivity']) ->with(['gender', 'subscription', 'leaderMemberships', 'ageGroupMemberships.subactivity'])
->withPendingPayment() ->withPendingPayment()
->ordered()
)->paginate(15)), )->paginate(15)),
]); ]);
} }

View File

@ -116,4 +116,14 @@ class Membership extends Model
{ {
return $query->active()->whereHas('activity', fn ($builder) => $builder->where('is_try', true)); return $query->active()->whereHas('activity', fn ($builder) => $builder->where('is_try', true));
} }
public static function booted(): void
{
static::saved(function ($membership) {
$membership->member->touch();
});
static::deleted(function ($membership) {
$membership->member->touch();
});
}
} }

View File

@ -133,10 +133,12 @@ return [
'meilisearch' => [ 'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY', null), 'key' => env('MEILI_MASTER_KEY', null),
'index-settings' => [ 'index-settings' => [
Member::class => [ Member::class => [
'filterableAttributes' => ['fullname', 'address'], 'filterableAttributes' => ['address', 'birthday', 'ausstand', 'bill_kind', 'group_id', 'memberships', 'id'],
'searchableAttributes' => ['fullname', 'address'],
'sortableAttributes' => ['lastname', 'firstname'],
] ]
], ],
], ],

View File

@ -0,0 +1,626 @@
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
DROP TABLE IF EXISTS `activities`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `activities` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`slug` varchar(255) NOT NULL,
`is_filterable` tinyint(1) NOT NULL DEFAULT 0,
`is_member` tinyint(1) NOT NULL DEFAULT 0,
`is_try` tinyint(1) NOT NULL DEFAULT 0,
`nami_id` int(10) unsigned DEFAULT NULL,
`has_efz` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `activity_subactivity`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `activity_subactivity` (
`activity_id` bigint(20) unsigned NOT NULL,
`subactivity_id` bigint(20) unsigned NOT NULL,
UNIQUE KEY `activity_subactivity_activity_id_subactivity_id_unique` (`activity_id`,`subactivity_id`),
KEY `activity_subactivity_subactivity_id_foreign` (`subactivity_id`),
CONSTRAINT `activity_subactivity_activity_id_foreign` FOREIGN KEY (`activity_id`) REFERENCES `activities` (`id`),
CONSTRAINT `activity_subactivity_subactivity_id_foreign` FOREIGN KEY (`subactivity_id`) REFERENCES `subactivities` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `confessions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `confessions` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`nami_id` int(10) unsigned DEFAULT NULL,
`is_null` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `countries`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `countries` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`nami_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `course_members`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `course_members` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`member_id` bigint(20) unsigned NOT NULL,
`course_id` bigint(20) unsigned NOT NULL,
`organizer` varchar(255) NOT NULL,
`event_name` varchar(255) NOT NULL,
`nami_id` int(10) unsigned NOT NULL,
`completed_at` date NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `course_members_member_id_foreign` (`member_id`),
KEY `course_members_course_id_foreign` (`course_id`),
CONSTRAINT `course_members_course_id_foreign` FOREIGN KEY (`course_id`) REFERENCES `courses` (`id`),
CONSTRAINT `course_members_member_id_foreign` FOREIGN KEY (`member_id`) REFERENCES `members` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `courses`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `courses` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`nami_id` int(10) unsigned NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `failed_jobs`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `failed_jobs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`connection` text NOT NULL,
`queue` text NOT NULL,
`payload` longtext NOT NULL,
`exception` longtext NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `fees`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `fees` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`nami_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `genders`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `genders` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`nami_id` int(10) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `groups`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `groups` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`nami_id` int(10) unsigned NOT NULL,
`parent_id` bigint(20) unsigned DEFAULT NULL,
`inner_name` varchar(255) NOT NULL,
`level` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `groups_parent_id_foreign` (`parent_id`),
CONSTRAINT `groups_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `groups` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `invoice_positions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `invoice_positions` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`invoice_id` bigint(20) unsigned NOT NULL,
`description` varchar(255) NOT NULL,
`member_id` bigint(20) unsigned NOT NULL,
`price` bigint(20) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `invoices`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `invoices` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`to` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`to`)),
`greeting` varchar(255) NOT NULL,
`status` varchar(255) NOT NULL,
`sent_at` date DEFAULT NULL,
`via` varchar(255) NOT NULL,
`usage` varchar(255) NOT NULL,
`mail_email` varchar(255) DEFAULT NULL,
`last_remembered_at` datetime DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `job_batches`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `job_batches` (
`id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`total_jobs` int(11) NOT NULL,
`pending_jobs` int(11) NOT NULL,
`failed_jobs` int(11) NOT NULL,
`failed_job_ids` longtext NOT NULL,
`options` mediumtext DEFAULT NULL,
`cancelled_at` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`finished_at` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `localmaildispatchers`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `localmaildispatchers` (
`id` char(36) NOT NULL,
`from` varchar(255) NOT NULL,
`to` varchar(255) NOT NULL,
UNIQUE KEY `localmaildispatchers_from_to_unique` (`from`,`to`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `maildispatchers`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `maildispatchers` (
`id` char(36) NOT NULL,
`name` varchar(255) NOT NULL,
`gateway_id` char(36) NOT NULL,
`filter` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`filter`))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `mailgateways`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `mailgateways` (
`id` char(36) NOT NULL,
`name` varchar(255) NOT NULL,
`domain` varchar(255) NOT NULL,
`type` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`type`)),
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `members`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `members` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`firstname` varchar(255) NOT NULL,
`lastname` varchar(255) NOT NULL,
`nickname` varchar(255) DEFAULT NULL,
`gender_id` bigint(20) unsigned DEFAULT NULL,
`country_id` bigint(20) unsigned DEFAULT NULL,
`other_country` varchar(255) DEFAULT NULL,
`confession_id` bigint(20) unsigned DEFAULT NULL,
`birthday` date DEFAULT NULL,
`joined_at` date DEFAULT NULL,
`send_newspaper` tinyint(1) NOT NULL,
`address` varchar(255) DEFAULT NULL,
`further_address` varchar(255) DEFAULT NULL,
`zip` varchar(255) DEFAULT NULL,
`location` varchar(255) DEFAULT NULL,
`group_id` bigint(20) unsigned NOT NULL,
`region_id` bigint(20) unsigned DEFAULT NULL,
`main_phone` varchar(255) DEFAULT NULL,
`mobile_phone` varchar(255) DEFAULT NULL,
`work_phone` varchar(255) DEFAULT NULL,
`fax` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`email_parents` varchar(255) DEFAULT NULL,
`nami_id` int(11) DEFAULT NULL,
`nationality_id` bigint(20) unsigned DEFAULT NULL,
`letter_address` text DEFAULT NULL,
`bill_kind` varchar(255) DEFAULT NULL,
`version` int(10) unsigned NOT NULL DEFAULT 1,
`children_phone` varchar(255) DEFAULT NULL,
`subscription_id` bigint(20) unsigned DEFAULT 1,
`efz` date DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`ps_at` date DEFAULT NULL,
`more_ps_at` date DEFAULT NULL,
`without_education_at` date DEFAULT NULL,
`without_efz_at` date DEFAULT NULL,
`has_svk` tinyint(1) NOT NULL DEFAULT 0,
`has_vk` tinyint(1) NOT NULL DEFAULT 0,
`multiply_pv` tinyint(1) NOT NULL DEFAULT 0,
`multiply_more_pv` tinyint(1) NOT NULL DEFAULT 0,
`slug` varchar(100) NOT NULL,
`salutation` varchar(255) DEFAULT NULL,
`comment` text DEFAULT NULL,
`mitgliedsnr` varchar(255) DEFAULT NULL,
`lat` double DEFAULT NULL,
`lon` double DEFAULT NULL,
`recertified_at` date DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `members_gender_id_foreign` (`gender_id`),
KEY `members_country_id_foreign` (`country_id`),
KEY `members_confession_id_foreign` (`confession_id`),
KEY `members_group_id_foreign` (`group_id`),
KEY `members_region_id_foreign` (`region_id`),
KEY `members_nationality_id_foreign` (`nationality_id`),
KEY `members_bill_kind_id_foreign` (`bill_kind`),
KEY `members_subscription_id_foreign` (`subscription_id`),
CONSTRAINT `members_confession_id_foreign` FOREIGN KEY (`confession_id`) REFERENCES `confessions` (`id`),
CONSTRAINT `members_country_id_foreign` FOREIGN KEY (`country_id`) REFERENCES `countries` (`id`),
CONSTRAINT `members_gender_id_foreign` FOREIGN KEY (`gender_id`) REFERENCES `genders` (`id`),
CONSTRAINT `members_group_id_foreign` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`),
CONSTRAINT `members_nationality_id_foreign` FOREIGN KEY (`nationality_id`) REFERENCES `nationalities` (`id`),
CONSTRAINT `members_region_id_foreign` FOREIGN KEY (`region_id`) REFERENCES `regions` (`id`),
CONSTRAINT `members_subscription_id_foreign` FOREIGN KEY (`subscription_id`) REFERENCES `subscriptions` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `memberships`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `memberships` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`group_id` bigint(20) unsigned NOT NULL,
`member_id` bigint(20) unsigned NOT NULL,
`nami_id` int(10) unsigned DEFAULT NULL,
`from` datetime NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`activity_id` bigint(20) unsigned NOT NULL,
`subactivity_id` bigint(20) unsigned DEFAULT NULL,
`promised_at` date DEFAULT NULL,
`to` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `memberships_unique` (`activity_id`,`subactivity_id`,`group_id`,`member_id`,`nami_id`),
KEY `memberships_group_id_foreign` (`group_id`),
KEY `memberships_subactivity_id_foreign` (`subactivity_id`),
KEY `memberships_member_id_foreign` (`member_id`),
CONSTRAINT `memberships_activity_id_foreign` FOREIGN KEY (`activity_id`) REFERENCES `activities` (`id`),
CONSTRAINT `memberships_group_id_foreign` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`),
CONSTRAINT `memberships_member_id_foreign` FOREIGN KEY (`member_id`) REFERENCES `members` (`id`),
CONSTRAINT `memberships_subactivity_id_foreign` FOREIGN KEY (`subactivity_id`) REFERENCES `subactivities` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `migrations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`migration` varchar(255) NOT NULL,
`batch` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `nationalities`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `nationalities` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`nami_id` int(10) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `oauth_access_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `oauth_access_tokens` (
`id` varchar(100) NOT NULL,
`user_id` bigint(20) unsigned DEFAULT NULL,
`client_id` bigint(20) unsigned NOT NULL,
`name` varchar(255) DEFAULT NULL,
`scopes` text DEFAULT NULL,
`revoked` tinyint(1) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`expires_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `oauth_access_tokens_user_id_index` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `oauth_auth_codes`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `oauth_auth_codes` (
`id` varchar(100) NOT NULL,
`user_id` bigint(20) unsigned NOT NULL,
`client_id` bigint(20) unsigned NOT NULL,
`scopes` text DEFAULT NULL,
`revoked` tinyint(1) NOT NULL,
`expires_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `oauth_auth_codes_user_id_index` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `oauth_clients`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `oauth_clients` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned DEFAULT NULL,
`name` varchar(255) NOT NULL,
`secret` varchar(100) DEFAULT NULL,
`provider` varchar(255) DEFAULT NULL,
`redirect` text NOT NULL,
`personal_access_client` tinyint(1) NOT NULL,
`password_client` tinyint(1) NOT NULL,
`revoked` tinyint(1) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `oauth_clients_user_id_index` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `oauth_personal_access_clients`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `oauth_personal_access_clients` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`client_id` bigint(20) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `oauth_refresh_tokens`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `oauth_refresh_tokens` (
`id` varchar(100) NOT NULL,
`access_token_id` varchar(100) NOT NULL,
`revoked` tinyint(1) NOT NULL,
`expires_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `oauth_refresh_tokens_access_token_id_index` (`access_token_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `password_resets`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `password_resets` (
`email` varchar(255) NOT NULL,
`token` varchar(255) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
KEY `password_resets_email_index` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `regions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `regions` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`is_null` tinyint(1) NOT NULL,
`nami_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `settings`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `settings` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`group` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`locked` tinyint(1) NOT NULL,
`payload` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`payload`)),
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `settings_group_index` (`group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `statuses`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `statuses` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`is_bill` tinyint(1) NOT NULL,
`is_remember` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `subactivities`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `subactivities` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`slug` varchar(255) NOT NULL,
`is_age_group` tinyint(1) NOT NULL DEFAULT 0,
`is_filterable` tinyint(1) NOT NULL DEFAULT 0,
`nami_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `subscription_children`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `subscription_children` (
`id` char(36) NOT NULL,
`parent_id` bigint(20) unsigned NOT NULL,
`name` varchar(255) NOT NULL,
`amount` int(10) unsigned NOT NULL,
KEY `subscription_children_parent_id_foreign` (`parent_id`),
CONSTRAINT `subscription_children_parent_id_foreign` FOREIGN KEY (`parent_id`) REFERENCES `subscriptions` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `subscriptions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `subscriptions` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`fee_id` bigint(20) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `subscriptions_fee_id_foreign` (`fee_id`),
CONSTRAINT `subscriptions_fee_id_foreign` FOREIGN KEY (`fee_id`) REFERENCES `fees` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `telescope_entries`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `telescope_entries` (
`sequence` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`uuid` char(36) NOT NULL,
`batch_id` char(36) NOT NULL,
`family_hash` varchar(255) DEFAULT NULL,
`should_display_on_index` tinyint(1) NOT NULL DEFAULT 1,
`type` varchar(20) NOT NULL,
`content` longtext NOT NULL,
`created_at` datetime DEFAULT NULL,
PRIMARY KEY (`sequence`),
UNIQUE KEY `telescope_entries_uuid_unique` (`uuid`),
KEY `telescope_entries_batch_id_index` (`batch_id`),
KEY `telescope_entries_family_hash_index` (`family_hash`),
KEY `telescope_entries_created_at_index` (`created_at`),
KEY `telescope_entries_type_should_display_on_index_index` (`type`,`should_display_on_index`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `telescope_entries_tags`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `telescope_entries_tags` (
`entry_uuid` char(36) NOT NULL,
`tag` varchar(255) NOT NULL,
KEY `telescope_entries_tags_entry_uuid_tag_index` (`entry_uuid`,`tag`),
KEY `telescope_entries_tags_tag_index` (`tag`),
CONSTRAINT `telescope_entries_tags_entry_uuid_foreign` FOREIGN KEY (`entry_uuid`) REFERENCES `telescope_entries` (`uuid`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `telescope_monitoring`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `telescope_monitoring` (
`tag` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL,
`remember_token` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `ways`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ways` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
INSERT INTO `migrations` VALUES (1,'2010_08_08_100000_create_telescope_entries_table',1);
INSERT INTO `migrations` VALUES (2,'2010_08_19_000000_create_failed_jobs_table',1);
INSERT INTO `migrations` VALUES (3,'2014_10_12_100000_create_password_resets_table',1);
INSERT INTO `migrations` VALUES (4,'2016_06_01_000001_create_oauth_auth_codes_table',1);
INSERT INTO `migrations` VALUES (5,'2016_06_01_000002_create_oauth_access_tokens_table',1);
INSERT INTO `migrations` VALUES (6,'2016_06_01_000003_create_oauth_refresh_tokens_table',1);
INSERT INTO `migrations` VALUES (7,'2016_06_01_000004_create_oauth_clients_table',1);
INSERT INTO `migrations` VALUES (8,'2016_06_01_000005_create_oauth_personal_access_clients_table',1);
INSERT INTO `migrations` VALUES (9,'2017_01_14_235348_create_nationalities_table',1);
INSERT INTO `migrations` VALUES (10,'2017_01_22_235143_create_subscriptions_table',1);
INSERT INTO `migrations` VALUES (11,'2017_04_12_010000_create_groups_table',1);
INSERT INTO `migrations` VALUES (12,'2017_07_04_235624_create_countries_table',1);
INSERT INTO `migrations` VALUES (13,'2017_07_05_000438_create_genders_table',1);
INSERT INTO `migrations` VALUES (14,'2017_07_05_000810_create_regions_table',1);
INSERT INTO `migrations` VALUES (15,'2017_07_05_001729_create_confessions_table',1);
INSERT INTO `migrations` VALUES (16,'2017_12_25_231219_create_ways_table',1);
INSERT INTO `migrations` VALUES (17,'2018_01_16_012910_create_activities_table',1);
INSERT INTO `migrations` VALUES (18,'2020_04_12_223230_create_members_table',1);
INSERT INTO `migrations` VALUES (19,'2021_07_04_101300_create_payments_table',1);
INSERT INTO `migrations` VALUES (20,'2021_11_18_001427_create_courses_table',1);
INSERT INTO `migrations` VALUES (21,'2021_11_18_215522_create_settings_table',1);
INSERT INTO `migrations` VALUES (22,'2021_11_18_230152_create_general_settings',1);
INSERT INTO `migrations` VALUES (23,'2021_11_22_233113_create_allowed_nami_login_setting',1);
INSERT INTO `migrations` VALUES (24,'2021_11_23_173033_create_users_table',1);
INSERT INTO `migrations` VALUES (25,'2022_01_18_205354_create_memberships_table',1);
INSERT INTO `migrations` VALUES (26,'2022_02_17_011021_create_groups_parent_id_column',1);
INSERT INTO `migrations` VALUES (27,'2022_02_19_230152_create_nami_settings',1);
INSERT INTO `migrations` VALUES (28,'2022_03_15_152907_create_members_efz_column',1);
INSERT INTO `migrations` VALUES (29,'2022_03_20_145006_create_activities_has_efz_column',1);
INSERT INTO `migrations` VALUES (30,'2022_04_28_203444_create_members_prevention_column',1);
INSERT INTO `migrations` VALUES (31,'2022_05_01_185012_create_nami_settings_group',1);
INSERT INTO `migrations` VALUES (32,'2022_08_30_132503_create_users_email_verified_add_column',1);
INSERT INTO `migrations` VALUES (33,'2022_09_05_213938_bill_settings',1);
INSERT INTO `migrations` VALUES (34,'2022_10_05_171451_create_members_slug_column',1);
INSERT INTO `migrations` VALUES (35,'2022_10_18_123917_mailman_settings',1);
INSERT INTO `migrations` VALUES (36,'2022_10_18_123918_mailman_is_active',1);
INSERT INTO `migrations` VALUES (37,'2022_10_18_123919_mailman_lists',1);
INSERT INTO `migrations` VALUES (38,'2022_11_05_213938_iban_settings',1);
INSERT INTO `migrations` VALUES (39,'2022_11_08_154408_create_memberships_activity_column',1);
INSERT INTO `migrations` VALUES (40,'2022_11_23_220958_drop_members_confirmed_at_column',1);
INSERT INTO `migrations` VALUES (41,'2022_12_06_211901_edit_members_bill_kind_id_column',1);
INSERT INTO `migrations` VALUES (42,'2022_12_11_192600_create_memberships_has_promise_column',1);
INSERT INTO `migrations` VALUES (43,'2022_12_13_203644_create_subscription_children_table',1);
INSERT INTO `migrations` VALUES (44,'2023_02_05_233824_create_memberships_to_column',1);
INSERT INTO `migrations` VALUES (45,'2023_02_05_235353_edit_activities_nami_id_column',1);
INSERT INTO `migrations` VALUES (46,'2023_02_12_223932_create_job_batches_table',1);
INSERT INTO `migrations` VALUES (47,'2023_02_26_192658_create_members_search_column',1);
INSERT INTO `migrations` VALUES (48,'2023_02_27_213656_create_members_salutation_column',1);
INSERT INTO `migrations` VALUES (49,'2023_02_27_221231_create_members_comment_column',1);
INSERT INTO `migrations` VALUES (50,'2023_03_02_220832_create_members_mitgliedsnr_column',1);
INSERT INTO `migrations` VALUES (51,'2023_03_05_230539_edit_members_birthday_column',1);
INSERT INTO `migrations` VALUES (52,'2023_03_05_231245_edit_members_address_column',1);
INSERT INTO `migrations` VALUES (53,'2023_05_01_185012_create_nami_search_setting',1);
INSERT INTO `migrations` VALUES (54,'2023_05_16_113849_create_members_latlon_column',1);
INSERT INTO `migrations` VALUES (55,'2023_06_01_102056_create_mailgateways_table',1);
INSERT INTO `migrations` VALUES (56,'2023_06_12_083133_create_maildispatchers_table',1);
INSERT INTO `migrations` VALUES (57,'2023_06_12_093205_create_localmaildispatchers_table',1);
INSERT INTO `migrations` VALUES (58,'2023_07_23_203851_edit_members_table',1);
INSERT INTO `migrations` VALUES (59,'2023_11_16_101137_create_module_settings',1);
INSERT INTO `migrations` VALUES (60,'2023_11_23_001310_create_invoice_data_column',1);
INSERT INTO `migrations` VALUES (61,'2023_11_24_131853_create_members_recertified_at_column',1);
INSERT INTO `migrations` VALUES (62,'2023_12_12_015320_create_invoices_table',1);
INSERT INTO `migrations` VALUES (63,'2023_12_30_012316_create_groups_inner_name_column',1);
INSERT INTO `migrations` VALUES (64,'2024_01_25_213922_drop_members_search_text_column',1);

View File

@ -104,8 +104,7 @@ services:
meilisearch: meilisearch:
image: getmeili/meilisearch:v1.6 image: getmeili/meilisearch:v1.6
command: 'meilisearch --master-key="abc"'
volumes: volumes:
- ./data/meilisearch:/meili_data - ./data/meilisearch:/meili_data
ports: env_file:
- '7700:7700' - .app.env

View File

@ -340,7 +340,6 @@ export default {
} }
}, },
onInput(v) { onInput(v) {
console.log(this.modelModifiers);
if (this.mode === 'none') { if (this.mode === 'none') {
this.transformedValue = v.target.value; this.transformedValue = v.target.value;
} }

21
resources/js/composables/useSearch.js vendored Normal file
View File

@ -0,0 +1,21 @@
import {inject} from 'vue';
export default function useSearch() {
const axios = inject('axios');
async function search(text, filters = []) {
var response = await axios.post(
document.querySelector('meta[name="meilisearch_baseurl"]').content + '/indexes/members/search',
{
q: text,
filter: filters,
},
{headers: {Authorization: 'Bearer ' + document.querySelector('meta[name="meilisearch_key"]').content}}
);
return response.data;
}
return {
search,
};
}

View File

@ -1,69 +1,49 @@
<template> <template>
<page-layout> <page-layout>
<form target="_BLANK" class="max-w-4xl w-full mx-auto gap-6 grid-cols-2 grid p-6"> <form target="_BLANK" class="max-w-4xl w-full mx-auto gap-6 grid-cols-2 grid p-6">
<f-text id="eventName" v-model="values.eventName" name="eventName" class="col-span-2" label="Veranstaltungs-Name" required></f-text> <f-text id="eventName" v-model="values.eventName" name="eventName" class="col-span-2"
label="Veranstaltungs-Name" required></f-text>
<f-text id="dateFrom" v-model="values.dateFrom" name="dateFrom" type="date" label="Datum von" required></f-text> <f-text id="dateFrom" v-model="values.dateFrom" name="dateFrom" type="date" label="Datum von" required></f-text>
<f-text id="dateUntil" v-model="values.dateUntil" name="dateUntil" type="date" label="Datum bis" required></f-text> <f-text id="dateUntil" v-model="values.dateUntil" name="dateUntil" type="date" label="Datum bis"
required></f-text>
<f-text id="zipLocation" v-model="values.zipLocation" name="zipLocation" label="PLZ / Ort" required></f-text> <f-text id="zipLocation" v-model="values.zipLocation" name="zipLocation" label="PLZ / Ort" required></f-text>
<f-select id="country" v-model="values.country" :options="countries" name="country" label="Land" required></f-select> <f-select id="country" v-model="values.country" :options="countries" name="country" label="Land"
required></f-select>
<div class="border-gray-200 shadow shadow-primary-700 p-3 shadow-[0_0_4px_gray] col-span-2"> <div class="border-gray-200 shadow shadow-primary-700 p-3 shadow-[0_0_4px_gray] col-span-2">
<f-text <f-text id="search_text" ref="searchTextField" v-model="searchText" class="col-span-2" name="search_text"
id="search_text" label="Suchen …" size="sm" @keypress.enter.prevent="onSubmitFirstMemberResult"></f-text>
ref="search_text_field"
v-model="searchText"
class="col-span-2"
name="search_text"
label="Suchen …"
size="sm"
@keypress.enter.prevent="onSubmitFirstMemberResult"
></f-text>
<div class="mt-2 grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-2 col-span-2"> <div class="mt-2 grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-2 col-span-2">
<f-switch <f-switch v-for="member in results" :id="`members-${member.id}`" :key="member.id"
v-for="member in search.results" v-model="values.members" :label="member.fullname" name="members[]" :value="member.id" size="sm"
:id="`members-${member.id}`" inline @keypress.enter.prevent="onSubmitMemberResult(member)"></f-switch>
:key="member.id"
v-model="values.members"
:label="`${member.firstname} ${member.lastname}`"
name="members[]"
:value="member.id"
size="sm"
inline
@keypress.enter.prevent="onSubmitMemberResult(member)"
></f-switch>
</div> </div>
</div> </div>
<button <button v-for="(compiler, index) in compilers" class="btn btn-primary mt-3 inline-block"
v-for="(compiler, index) in compilers" @click.prevent="submit(compiler.class)" v-text="compiler.title"></button>
class="btn btn-primary mt-3 inline-block"
@click.prevent="
values.type = compiler.class;
submit();
"
v-text="compiler.title"
></button>
</form> </form>
</page-layout> </page-layout>
</template> </template>
<script> <script setup>
import debounce from 'lodash/debounce'; import { ref, computed, inject } from 'vue';
import useSearch from '../../composables/useSearch.js';
const axios = inject('axios');
export default { const { search } = useSearch();
props: {
const props = defineProps({
data: {}, data: {},
countries: {}, countries: {},
compilers: {}, compilers: {},
}, });
data: function () {
return { const searchRaw = ref('');
search: { const results = ref([]);
s: '', const searchTextField = ref([]);
results: [], const values = ref({
},
values: {
type: null, type: null,
members: [], members: [],
event_name: '', event_name: '',
@ -71,64 +51,40 @@ export default {
dateUntil: '', dateUntil: '',
zipLocation: '', zipLocation: '',
country: null, country: null,
...this.data, ...props.data,
}, });
};
},
computed: {
searchText: {
get() {
return this.search.s;
},
set: function (event) {
this.search.s = event;
debounce(async () => { const searchText = computed({
var response = await this.axios.post( get: () => searchRaw.value,
'/api/member/search', set: async (v) => {
{ searchRaw.value = v;
filter: {
search: event,
hasBirthday: true,
hasFullAddress: true,
},
},
{headers: {'X-Meta': 'false'}}
);
this.search.results = response.data.data; results.value = (await search(v, ['birthday IS NOT NULL', 'address IS NOT EMPTY'])).hits;
}, 300)();
}, },
}, });
},
methods: { async function submit(compiler) {
async submit() { values.value.type = compiler;
try { await axios.post('/contribution-validate', values.value);
await this.axios.post('/contribution-validate', this.values); var payload = btoa(encodeURIComponent(JSON.stringify(values.value)));
var payload = btoa(encodeURIComponent(JSON.stringify(this.values)));
window.open(`/contribution-generate?payload=${payload}`); window.open(`/contribution-generate?payload=${payload}`);
} catch (e) {
this.errorsFromException(e);
} }
}, function onSubmitMemberResult(selected) {
onSubmitMemberResult(selected) { if (values.value.members.find((m) => m === selected.id) !== undefined) {
if (this.values.members.find((m) => m === selected.id) !== undefined) { values.value.members = values.value.members.filter((m) => m !== selected.id);
this.values.members = this.values.members.filter((m) => m === selected.id);
} else { } else {
this.values.members.push(selected.id); values.value.members.push(selected.id);
} }
this.searchText = ''; searchRaw.value = '';
this.$refs.search_text_field.$el.querySelector('input').focus(); searchTextField.value.$el.querySelector('input').focus();
}, }
onSubmitFirstMemberResult() { function onSubmitFirstMemberResult() {
if (this.search.results.length === 0) { if (results.value.length === 0) {
this.searchText = ''; searchRaw.value = '';
return; return;
} }
this.onSubmitMemberResult(this.search.results[0]); onSubmitMemberResult(results.value[0]);
}, }
},
};
</script> </script>

View File

@ -10,27 +10,56 @@
<ui-box heading="Metadatem"> <ui-box heading="Metadatem">
<div class="grid gap-4 sm:grid-cols-2"> <div class="grid gap-4 sm:grid-cols-2">
<f-text id="name" v-model="model.name" name="name" label="Name" size="sm" required></f-text> <f-text id="name" v-model="model.name" name="name" label="Name" size="sm" required></f-text>
<f-select id="gateway_id" v-model="model.gateway_id" name="gateway_id" :options="meta.gateways" <f-select id="gateway_id" v-model="model.gateway_id" name="gateway_id" :options="meta.gateways" label="Verbindung" size="sm" required></f-select>
label="Verbindung" size="sm" required></f-select>
</div> </div>
</ui-box> </ui-box>
<ui-box v-if="members !== null" heading="Filterregeln"> <ui-box v-if="members !== null" heading="Filterregeln">
<div class="grid gap-4 sm:grid-cols-2"> <div class="grid gap-4 sm:grid-cols-2">
<f-multipleselect id="activity_ids" v-model="model.filter.activity_ids" name="activity_ids" <f-multipleselect
:options="members.meta.filterActivities" label="Tätigkeit" size="sm" id="activity_ids"
@update:model-value="reload(1)"></f-multipleselect> v-model="model.filter.activity_ids"
<f-multipleselect id="subactivity_ids" v-model="model.filter.subactivity_ids" name="subactivity_ids" name="activity_ids"
:options="members.meta.filterSubactivities" label="Unterttätigkeit" size="sm" :options="members.meta.filterActivities"
@update:model-value="reload(1)"></f-multipleselect> label="Tätigkeit"
<f-multipleselect id="include" v-model="model.filter.include" name="include" size="sm"
:options="members.meta.members" label="Zusätzliche Mitglieder" size="sm" @update:model-value="reload(1)"
@update:model-value="reload(1)"></f-multipleselect> ></f-multipleselect>
<f-multipleselect id="exclude" v-model="model.filter.exclude" name="exclude" <f-multipleselect
:options="members.meta.members" label="Mitglieder ausschließen" size="sm" id="subactivity_ids"
@update:model-value="reload(1)"></f-multipleselect> v-model="model.filter.subactivity_ids"
<f-multipleselect id="groupIds" v-model="model.filter.group_ids" name="groupIds" name="subactivity_ids"
:options="members.meta.groups" label="Gruppierungen" size="sm" :options="members.meta.filterSubactivities"
@update:model-value="reload(1)"></f-multipleselect> label="Unterttätigkeit"
size="sm"
@update:model-value="reload(1)"
></f-multipleselect>
<f-multipleselect
id="include"
v-model="model.filter.include"
name="include"
:options="members.meta.members"
label="Zusätzliche Mitglieder"
size="sm"
@update:model-value="reload(1)"
></f-multipleselect>
<f-multipleselect
id="exclude"
v-model="model.filter.exclude"
name="exclude"
:options="members.meta.members"
label="Mitglieder ausschließen"
size="sm"
@update:model-value="reload(1)"
></f-multipleselect>
<f-multipleselect
id="groupIds"
v-model="model.filter.group_ids"
name="groupIds"
:options="members.meta.groups"
label="Gruppierungen"
size="sm"
@update:model-value="reload(1)"
></f-multipleselect>
</div> </div>
</ui-box> </ui-box>
<ui-box v-if="members !== null" heading="Mitglieder"> <ui-box v-if="members !== null" heading="Mitglieder">
@ -72,7 +101,7 @@ const props = defineProps({
}, },
}); });
const { toFilterString, router } = useIndex({ data: [], meta: {} }, 'maildispatcher'); const {router} = useIndex({data: [], meta: {}}, 'maildispatcher');
const model = ref(props.data === undefined ? {...props.meta.default_model} : {...props.data}); const model = ref(props.data === undefined ? {...props.meta.default_model} : {...props.data});
const members = ref(null); const members = ref(null);
@ -81,8 +110,8 @@ const axios = inject('axios');
async function reload(page) { async function reload(page) {
members.value = ( members.value = (
await axios.post('/api/member/search', { await axios.post('/api/member/search', {
page: page || 1, page: page,
filter: toFilterString(model.value.filter), filter: model.value.filter,
}) })
).data; ).data;
} }

View File

@ -4,6 +4,10 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<meta name="socketport" content="{{env('SOCKET_PORT')}}" /> <meta name="socketport" content="{{env('SOCKET_PORT')}}" />
@if(auth()->id())
<meta name="meilisearch_key" content="{{config('scout.meilisearch.key')}}" />
<meta name="meilisearch_baseurl" content="http://localhost:7700" />
@endif
@vite('resources/js/app.js') @vite('resources/js/app.js')
</head> </head>
<body class="min-h-full flex flex-col"> <body class="min-h-full flex flex-col">

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Tests\Feature\Maildispatcher; namespace Tests\EndToEnd\Maildispatcher;
use \Mockery as M; use \Mockery as M;
use App\Activity; use App\Activity;
@ -10,12 +10,10 @@ use App\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Types\LocalType; use App\Mailgateway\Types\LocalType;
use App\Member\Member; use App\Member\Member;
use App\Member\Membership; use App\Member\Membership;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\EndToEndTestCase;
use Tests\TestCase;
class StoreTest extends TestCase class StoreTest extends EndToEndTestCase
{ {
use DatabaseTransactions;
public function setUp(): void public function setUp(): void
{ {
@ -31,6 +29,7 @@ class StoreTest extends TestCase
Member::factory()->defaults()->has(Membership::factory()->inLocal('Leiter*in', 'Wölfling'))->create(['email' => 'jane@example.com']); Member::factory()->defaults()->has(Membership::factory()->inLocal('Leiter*in', 'Wölfling'))->create(['email' => 'jane@example.com']);
$activityId = Activity::first()->id; $activityId = Activity::first()->id;
sleep(1);
$response = $this->postJson('/maildispatcher', [ $response = $this->postJson('/maildispatcher', [
'name' => 'test', 'name' => 'test',
'gateway_id' => $gateway->id, 'gateway_id' => $gateway->id,
@ -61,6 +60,7 @@ class StoreTest extends TestCase
Member::factory()->defaults()->create(['email' => 'jane@example.com']); Member::factory()->defaults()->create(['email' => 'jane@example.com']);
Member::factory()->defaults()->create(['email' => 'jane@example.com']); Member::factory()->defaults()->create(['email' => 'jane@example.com']);
sleep(1);
$this->postJson('/maildispatcher', [ $this->postJson('/maildispatcher', [
'name' => 'test', 'name' => 'test',
'gateway_id' => $gateway->id, 'gateway_id' => $gateway->id,
@ -73,6 +73,7 @@ class StoreTest extends TestCase
$gateway = Mailgateway::factory()->type(LocalType::class, [])->create(); $gateway = Mailgateway::factory()->type(LocalType::class, [])->create();
Member::factory()->defaults()->create(['email' => 'Jane@example.com']); Member::factory()->defaults()->create(['email' => 'Jane@example.com']);
sleep(1);
$this->postJson('/maildispatcher', [ $this->postJson('/maildispatcher', [
'name' => 'test', 'name' => 'test',
'gateway_id' => $gateway->id, 'gateway_id' => $gateway->id,

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Tests\Feature\Maildispatcher; namespace Tests\EndToEnd\Maildispatcher;
use App\Maildispatcher\Actions\ResyncAction; use App\Maildispatcher\Actions\ResyncAction;
use App\Maildispatcher\Models\Maildispatcher; use App\Maildispatcher\Models\Maildispatcher;
@ -8,13 +8,11 @@ use App\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Types\LocalType; use App\Mailgateway\Types\LocalType;
use App\Member\FilterScope; use App\Member\FilterScope;
use App\Member\Member; use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\TestCase; use Tests\EndToEndTestCase;
class UpdateTest extends TestCase class UpdateTest extends EndToEndTestCase
{ {
use DatabaseTransactions;
public function setUp(): void public function setUp(): void
{ {
parent::setUp(); parent::setUp();
@ -24,13 +22,15 @@ class UpdateTest extends TestCase
public function testItCanUpdateFilters(): void public function testItCanUpdateFilters(): void
{ {
$this->withoutExceptionHandling();
$dispatcher = Maildispatcher::factory() $dispatcher = Maildispatcher::factory()
->for(Mailgateway::factory()->type(LocalType::class, [])->domain('example.com'), 'gateway') ->for(Mailgateway::factory()->type(LocalType::class, [])->domain('example.com'), 'gateway')
->filter(FilterScope::from([])) ->filter(FilterScope::from([]))
->create(); ->create();
Member::factory()->defaults()->create(['email' => 'to@example.com']); Member::factory()->defaults()->create(['email' => 'to@example.com']);
$response = $this->patchJson("/maildispatcher/{$dispatcher->id}", [ sleep(1);
$this->patchJson("/maildispatcher/{$dispatcher->id}", [
'name' => 'test', 'name' => 'test',
'gateway_id' => $dispatcher->gateway->id, 'gateway_id' => $dispatcher->gateway->id,
'filter' => [], 'filter' => [],
@ -51,6 +51,7 @@ class UpdateTest extends TestCase
Member::factory()->defaults()->create(['email' => 'to@example.com']); Member::factory()->defaults()->create(['email' => 'to@example.com']);
ResyncAction::run(); ResyncAction::run();
sleep(1);
$response = $this->patchJson("/maildispatcher/{$dispatcher->id}", [ $response = $this->patchJson("/maildispatcher/{$dispatcher->id}", [
'name' => 'testa', 'name' => 'testa',
'gateway_id' => $dispatcher->gateway->id, 'gateway_id' => $dispatcher->gateway->id,
@ -73,6 +74,7 @@ class UpdateTest extends TestCase
->filter(FilterScope::from([])) ->filter(FilterScope::from([]))
->create(); ->create();
$member = Member::factory()->defaults()->create(['email' => 'to@example.com']); $member = Member::factory()->defaults()->create(['email' => 'to@example.com']);
sleep(1);
ResyncAction::run(); ResyncAction::run();
$member->update(['email' => 'to2@example.com']); $member->update(['email' => 'to2@example.com']);
ResyncAction::run(); ResyncAction::run();

View File

@ -13,15 +13,14 @@ use App\Membership\Actions\MembershipStoreAction;
use App\Subactivity; use App\Subactivity;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Queue;
use Tests\EndToEndTestCase;
use Tests\TestCase; use Tests\TestCase;
use Throwable; use Throwable;
use Zoomyboy\LaravelNami\Fakes\MembershipFake; use Zoomyboy\LaravelNami\Fakes\MembershipFake;
class MassstoreActionTest extends TestCase class MassstoreActionTest extends EndToEndTestCase
{ {
use DatabaseMigrations;
public function testItFiresActionJobWhenUsingController(): void public function testItFiresActionJobWhenUsingController(): void
{ {
Queue::fake(); Queue::fake();

View File

@ -1,19 +1,13 @@
<?php <?php
namespace Tests\Feature\Member; namespace Tests\EndToEnd\Member;
use App\Member\Member; use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Tests\TestCase; use Tests\EndToEndTestCase;
class ExportCsvActionTest extends TestCase class ExportCsvActionTest extends EndToEndTestCase
{ {
use DatabaseTransactions;
/**
* A basic feature test example.
*/
public function testItExportsACsvFile(): void public function testItExportsACsvFile(): void
{ {
Storage::fake('temp'); Storage::fake('temp');
@ -22,6 +16,7 @@ class ExportCsvActionTest extends TestCase
Member::factory()->defaults()->postBillKind()->create(['firstname' => 'Jane', 'main_phone' => '+49 176 70343221', 'email' => 'max@muster.de']); Member::factory()->defaults()->postBillKind()->create(['firstname' => 'Jane', 'main_phone' => '+49 176 70343221', 'email' => 'max@muster.de']);
Member::factory()->defaults()->emailBillKind()->create(['firstname' => 'Max']); Member::factory()->defaults()->emailBillKind()->create(['firstname' => 'Max']);
sleep(1);
$response = $this->callFilter('member-export', ['bill_kind' => 'Post']); $response = $this->callFilter('member-export', ['bill_kind' => 'Post']);
$response->assertDownload('mitglieder.csv'); $response->assertDownload('mitglieder.csv');
@ -31,4 +26,25 @@ class ExportCsvActionTest extends TestCase
$this->assertTrue(str_contains($contents, 'max@muster.de')); $this->assertTrue(str_contains($contents, 'max@muster.de'));
$this->assertFalse(str_contains($contents, 'Max')); $this->assertFalse(str_contains($contents, 'Max'));
} }
public function testItOrdersByLastname(): void
{
Storage::fake('temp');
$this->withoutExceptionHandling()->login()->loginNami();
Member::factory()->defaults()->create(['lastname' => 'C']);
Member::factory()->defaults()->create(['lastname' => 'A']);
sleep(1);
$response = $this->callFilter('member-export', []);
$response->assertDownload('mitglieder.csv');
$contents = Storage::disk('temp')->get('mitglieder.csv');
$this->assertEquals(['A', 'C'], collect(explode("\n", $contents))
->filter(fn ($line) => $line !== '')
->filter(fn ($line) => !str($line)->startsWith('Nachname'))
->map(fn ($line) => (string) str($line)->substr(0, 1))
->values()
->toArray());
}
} }

View File

@ -2,37 +2,31 @@
namespace Tests\EndToEnd; namespace Tests\EndToEnd;
use App\Activity;
use App\Group; use App\Group;
use App\Invoice\BillKind;
use App\Invoice\Enums\InvoiceStatus;
use App\Invoice\Models\Invoice; use App\Invoice\Models\Invoice;
use App\Invoice\Models\InvoicePosition; use App\Invoice\Models\InvoicePosition;
use App\Member\Member; use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseMigrations; use App\Member\Membership;
use Illuminate\Support\Facades\Artisan; use App\Subactivity;
use Laravel\Scout\Console\SyncIndexSettingsCommand; use Tests\EndToEndTestCase;
use Tests\TestCase;
class MemberIndexTest extends TestCase class MemberIndexTest extends EndToEndTestCase
{ {
use DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
config()->set('scout.driver', 'meilisearch');
Artisan::call(SyncIndexSettingsCommand::class);
}
public function testItHandlesFullTextSearch(): void public function testItHandlesFullTextSearch(): void
{ {
$this->withoutExceptionHandling()->login()->loginNami(); $this->withoutExceptionHandling()->login()->loginNami();
Member::factory()->defaults()->create(['firstname' => 'Alexander']); Member::factory()->defaults()->count(2)->create(['firstname' => 'Alexander']);
Member::factory()->defaults()->create(['firstname' => 'Heinrich']); Member::factory()->defaults()->create(['firstname' => 'Heinrich']);
sleep(5); sleep(1);
$response = $this->callFilter('member.index', ['search' => 'Alexander']); $this->callFilter('member.index', ['search' => 'Alexander'])
->assertInertiaCount('data.data', 2);
$this->assertCount(1, $this->inertia($response, 'data.data')); $this->callFilter('member.index', ['search' => 'Heinrich'])
->assertInertiaCount('data.data', 1);
} }
public function testItHandlesAddress(): void public function testItHandlesAddress(): void
@ -41,25 +35,138 @@ class MemberIndexTest extends TestCase
Member::factory()->defaults()->create(['address' => '']); Member::factory()->defaults()->create(['address' => '']);
Member::factory()->defaults()->create(['zip' => '']); Member::factory()->defaults()->create(['zip' => '']);
Member::factory()->defaults()->create(['location' => '']); Member::factory()->defaults()->create(['location' => '']);
Member::factory()->defaults()->create();
sleep(5); sleep(1);
$response = $this->callFilter('member.index', ['has_full_address' => true]); $this->callFilter('member.index', ['has_full_address' => false])
$noResponse = $this->callFilter('member.index', ['has_full_address' => false]); ->assertInertiaCount('data.data', 3)
->assertInertiaPath('data.meta.total', 3)
$this->assertCount(0, $this->inertia($response, 'data.data')); ->assertInertiaPath('data.meta.from', 1)
$this->assertCount(3, $this->inertia($noResponse, 'data.data')); ->assertInertiaPath('data.meta.to', 3);
$this->callFilter('member.index', ['has_full_address' => true])
->assertInertiaCount('data.data', 1)
->assertInertiaPath('data.meta.total', 1)
->assertInertiaPath('data.meta.from', 1)
->assertInertiaPath('data.meta.to', 1);
} }
public function testItHandlesBirthday(): void public function testItHandlesBirthday(): void
{ {
$this->withoutExceptionHandling()->login()->loginNami(); $this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()->defaults()->create(['birthday' => null]); Member::factory()->defaults()->create(['birthday' => null]);
Member::factory()->defaults()->count(2)->create();
sleep(5); sleep(1);
$response = $this->callFilter('member.index', ['has_birthday' => true]); $this->callFilter('member.index', ['has_birthday' => true])
->assertInertiaCount('data.data', 2);
$this->callFilter('member.index', ['has_birthday' => false])
->assertInertiaCount('data.data', 1);
}
$this->assertCount(0, $this->inertia($response, 'data.data')); public function testItHandlesBillKind(): void
$member->delete(); {
$this->withoutExceptionHandling()->login()->loginNami();
Member::factory()->defaults()->postBillKind()->create();
Member::factory()->defaults()->emailBillKind()->count(2)->create();
sleep(1);
$this->callFilter('member.index', ['bill_kind' => BillKind::POST->value])
->assertInertiaCount('data.data', 1);
$this->callFilter('member.index', ['bill_kind' => BillKind::EMAIL->value])
->assertInertiaCount('data.data', 2);
$this->callFilter('member.index', [])
->assertInertiaCount('data.data', 3);
}
public function testItHandlesGroupIds(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$group = Group::factory()->create();
$otherGroup = Group::factory()->create();
$thirdGroup = Group::factory()->create();
Member::factory()->defaults()->for($group)->create();
Member::factory()->defaults()->count(2)->for($otherGroup)->create();
Member::factory()->defaults()->count(3)->for($thirdGroup)->create();
sleep(1);
$this->callFilter('member.index', ['group_ids' => [$group->id]])
->assertInertiaCount('data.data', 1);
$this->callFilter('member.index', ['group_ids' => [$otherGroup->id]])
->assertInertiaCount('data.data', 2);
$this->callFilter('member.index', ['group_ids' => [$otherGroup->id, $thirdGroup->id]])
->assertInertiaCount('data.data', 5);
$this->callFilter('member.index', [])
->assertInertiaCount('data.data', 6);
}
public function testItHandlesActivitiesAndSubactivities(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$mitglied = Activity::factory()->name('€ Mitglied')->create();
$schnuppermitglied = Activity::factory()->name('Schnuppermitgliedschaft')->create();
$woelfling = Subactivity::factory()->name('Wölfling')->create();
$rover = Subactivity::factory()->name('Rover')->create();
Member::factory()->defaults()->create();
Member::factory()->defaults()->count(2)->has(Membership::factory()->for($mitglied)->for($woelfling))->create();
Member::factory()->defaults()->count(3)->has(Membership::factory()->for($schnuppermitglied)->for($rover))->create();
sleep(1);
$this->callFilter('member.index', ['activity_ids' => [$mitglied->id]])
->assertInertiaCount('data.data', 2);
$this->callFilter('member.index', ['subactivity_ids' => [$woelfling->id]])
->assertInertiaCount('data.data', 2);
$this->callFilter('member.index', ['subactivity_ids' => [$rover->id]])
->assertInertiaCount('data.data', 3);
$this->callFilter('member.index', ['activity_ids' => [$schnuppermitglied->id], 'subactivity_ids' => [$woelfling->id]])
->assertInertiaCount('data.data', 0);
$this->callFilter('member.index', ['activity_ids' => [$schnuppermitglied->id], 'subactivity_ids' => [$rover->id]])
->assertInertiaCount('data.data', 3);
$this->callFilter('member.index', [])
->assertInertiaCount('data.data', 6);
}
public function testItHandlesActivityAndSubactivityForSingleMembership(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$mitglied = Activity::factory()->name('€ Mitglied')->create();
$schnuppermitglied = Activity::factory()->name('Schnuppermitgliedschaft')->create();
$woelfling = Subactivity::factory()->name('Wölfling')->create();
$rover = Subactivity::factory()->name('Rover')->create();
Member::factory()->defaults()
->has(Membership::factory()->for($mitglied)->for($woelfling))
->has(Membership::factory()->for($schnuppermitglied)->for($rover))
->create();
sleep(1);
$this->callFilter('member.index', ['activity_ids' => [$mitglied->id, 5, 6], 'subactivity_ids' => [$rover->id, 8, 9]])
->assertInertiaCount('data.data', 0);
}
public function testItIgnoresInactiveMemberships(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$mitglied = Activity::factory()->name('€ Mitglied')->create();
$woelfling = Subactivity::factory()->name('Wölfling')->create();
Member::factory()->defaults()->has(Membership::factory()->for($mitglied)->for($woelfling)->ended())->create();
sleep(1);
$this->callFilter('member.index', ['activity_ids' => [$mitglied->id]])
->assertInertiaCount('data.data', 0);
$this->callFilter('member.index', ['subactivity_ids' => [$woelfling->id]])
->assertInertiaCount('data.data', 0);
}
public function testItListensForMembershipDeletion(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$mitglied = Activity::factory()->name('€ Mitglied')->create();
$member = Member::factory()->defaults()->has(Membership::factory()->for($mitglied))->create();
$member->memberships->first()->delete();
sleep(1);
$this->callFilter('member.index', ['activity_ids' => [$mitglied->id]])
->assertInertiaCount('data.data', 0);
} }
public function testItFiltersForSearchButNotForPayments(): void public function testItFiltersForSearchButNotForPayments(): void
@ -67,12 +174,46 @@ class MemberIndexTest extends TestCase
$this->withoutExceptionHandling()->login()->loginNami(); $this->withoutExceptionHandling()->login()->loginNami();
Member::factory()->defaults() Member::factory()->defaults()
->has(InvoicePosition::factory()->for(Invoice::factory())) ->has(InvoicePosition::factory()->for(Invoice::factory()))
->create(['firstname' => '::firstname::']); ->create(['firstname' => 'firstname']);
Member::factory()->defaults()->create(['firstname' => '::firstname::']); Member::factory()->defaults()->create(['firstname' => 'firstname']);
sleep(5); sleep(1);
$response = $this->callFilter('member.index', ['search' => '::firstname::', 'ausstand' => true]); $this->callFilter('member.index', ['search' => 'firstname', 'ausstand' => true])
->assertInertiaCount('data.data', 1);
}
$this->assertCount(1, $this->inertia($response, 'data.data')); public function testItIgnoresPaidInvoices(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
Member::factory()->defaults()
->has(InvoicePosition::factory()->for(Invoice::factory()->status(InvoiceStatus::PAID)))
->create();
sleep(1);
$this->callFilter('member.index', ['ausstand' => true])
->assertInertiaCount('data.data', 0);
}
public function testItIncludesMembers(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()->defaults()->create(['birthday' => null, 'location' => '']);
sleep(1);
$this->callFilter('member.index', ['hasBirthday' => true, 'hasFullAddress' => false])
->assertInertiaCount('data.data', 0);
$this->callFilter('member.index', ['hasBirthday' => true, 'hasFullAddress' => false, 'include' => [$member->id]])
->assertInertiaCount('data.data', 1);
}
public function testItExcludesMembers(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()->defaults()->create(['birthday' => null]);
sleep(1);
$this->callFilter('member.index', ['hasBirthday' => false, 'exclude' => [$member->id]])
->assertInertiaCount('data.data', 0);
} }
} }

View File

@ -0,0 +1,23 @@
<?php
namespace Tests\EndToEnd;
use App\Member\Member;
use Tests\EndToEndTestCase;
class MemberSearchTest extends EndToEndTestCase
{
public function testItHandlesFullTextSearch(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
Member::factory()->defaults()->count(2)->create(['firstname' => 'Alexander']);
Member::factory()->defaults()->create(['firstname' => 'Heinrich']);
sleep(1);
$this->post(route('member.search'), ['filter' => ['search' => 'Alexander']])
->assertJsonCount(2, 'data');
$this->post(route('member.search'), ['filter' => ['search' => 'Heinrich']])
->assertJsonCount(1, 'data');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Tests;
use Illuminate\Foundation\Testing\DatabaseMigrations;
abstract class EndToEndTestCase extends TestCase
{
use DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
$this->useMeilisearch();
}
}

View File

@ -9,15 +9,19 @@ use App\Invoice\Models\Invoice;
use App\Invoice\Models\InvoicePosition; use App\Invoice\Models\InvoicePosition;
use App\Member\Member; use App\Member\Member;
use App\Member\Membership; use App\Member\Membership;
use App\Payment\Payment;
use App\Subactivity; use App\Subactivity;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\RequestFactories\Child;
use Tests\TestCase; use Tests\TestCase;
class IndexTest extends TestCase class IndexTest extends TestCase
{ {
use DatabaseTransactions; use DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
$this->useMeilisearch();
}
public function testItGetsMembers(): void public function testItGetsMembers(): void
{ {
@ -31,6 +35,7 @@ class IndexTest extends TestCase
'location' => 'Hilden', 'location' => 'Hilden',
]); ]);
sleep(1);
$response = $this->get('/member'); $response = $this->get('/member');
$this->assertComponent('member/VIndex', $response); $this->assertComponent('member/VIndex', $response);
@ -59,6 +64,7 @@ class IndexTest extends TestCase
'location' => null, 'location' => null,
]); ]);
sleep(1);
$response = $this->get('/member'); $response = $this->get('/member');
$this->assertInertiaHas(null, $response, 'data.data.0.birthday'); $this->assertInertiaHas(null, $response, 'data.data.0.birthday');
@ -82,6 +88,7 @@ class IndexTest extends TestCase
->defaults() ->defaults()
->create(['lastname' => 'C']); ->create(['lastname' => 'C']);
sleep(1);
$response = $this->get('/member'); $response = $this->get('/member');
$this->assertInertiaHas(url("/member/{$member->id}/efz"), $response, 'data.data.0.efz_link'); $this->assertInertiaHas(url("/member/{$member->id}/efz"), $response, 'data.data.0.efz_link');
@ -113,6 +120,7 @@ class IndexTest extends TestCase
->has(Membership::factory()->in('€ Mitglied', 123, 'Wölfling', 12)) ->has(Membership::factory()->in('€ Mitglied', 123, 'Wölfling', 12))
->create(); ->create();
sleep(1);
$response = $this->get('/member'); $response = $this->get('/member');
$this->assertInertiaHas('woelfling', $response, 'data.data.0.age_group_icon'); $this->assertInertiaHas('woelfling', $response, 'data.data.0.age_group_icon');
@ -121,7 +129,7 @@ class IndexTest extends TestCase
public function testAgeIsNullWhenBirthdayIsNull(): void public function testAgeIsNullWhenBirthdayIsNull(): void
{ {
$this->withoutExceptionHandling()->login()->loginNami(); $this->withoutExceptionHandling()->login()->loginNami();
$member = Member::factory()->defaults()->create(['birthday' => null]); Member::factory()->defaults()->create(['birthday' => null]);
$response = $this->get('/member'); $response = $this->get('/member');
@ -135,11 +143,11 @@ class IndexTest extends TestCase
$activity = Activity::factory()->hasAttached(Subactivity::factory()->name('Biber'))->name('€ Mitglied')->create(); $activity = Activity::factory()->hasAttached(Subactivity::factory()->name('Biber'))->name('€ Mitglied')->create();
$subactivity = $activity->subactivities->first(); $subactivity = $activity->subactivities->first();
$response = $this->get('/member'); sleep(1);
$this->get('/member')
$this->assertInertiaHas('Biber', $response, "data.meta.formSubactivities.{$activity->id}.{$subactivity->id}"); ->assertInertiaPath("data.meta.formSubactivities.{$activity->id}.{$subactivity->id}", 'Biber')
$this->assertInertiaHas('Biber', $response, "data.meta.filterSubactivities.{$subactivity->id}"); ->assertInertiaPath("data.meta.filterSubactivities.{$subactivity->id}", 'Biber')
$this->assertInertiaHas('€ Mitglied', $response, "data.meta.formActivities.{$activity->id}"); ->assertInertiaPath("data.meta.formActivities.{$activity->id}", '€ Mitglied');
} }
public function testItCanFilterForBillKinds(): void public function testItCanFilterForBillKinds(): void
@ -149,12 +157,12 @@ class IndexTest extends TestCase
Member::factory()->defaults()->postBillKind()->create(); Member::factory()->defaults()->postBillKind()->create();
Member::factory()->defaults()->postBillKind()->create(); Member::factory()->defaults()->postBillKind()->create();
$emailResponse = $this->callFilter('member.index', ['bill_kind' => 'E-Mail']); sleep(1);
$postResponse = $this->callFilter('member.index', ['bill_kind' => 'Post']); $this->callFilter('member.index', ['bill_kind' => 'E-Mail'])
->assertInertiaCount('data.data', 1)
$this->assertCount(1, $this->inertia($emailResponse, 'data.data')); ->assertInertiaPath('data.meta.filter.bill_kind', 'E-Mail');
$this->assertCount(2, $this->inertia($postResponse, 'data.data')); $this->callFilter('member.index', ['bill_kind' => 'Post'])
$this->assertInertiaHas('E-Mail', $emailResponse, 'data.meta.filter.bill_kind'); ->assertInertiaCount('data.data', 2);
} }
public function testItCanFilterForGroups(): void public function testItCanFilterForGroups(): void
@ -166,6 +174,7 @@ class IndexTest extends TestCase
Member::factory()->defaults()->for($group1)->create(); Member::factory()->defaults()->for($group1)->create();
Member::factory()->defaults()->for($group1)->create(); Member::factory()->defaults()->for($group1)->create();
sleep(1);
$oneResponse = $this->callFilter('member.index', ['group_ids' => [$group1->id]]); $oneResponse = $this->callFilter('member.index', ['group_ids' => [$group1->id]]);
$twoResponse = $this->callFilter('member.index', ['group_ids' => [$group2->id]]); $twoResponse = $this->callFilter('member.index', ['group_ids' => [$group2->id]]);
@ -174,24 +183,6 @@ class IndexTest extends TestCase
$this->assertInertiaHas([$group1->id], $oneResponse, 'data.meta.filter.group_ids'); $this->assertInertiaHas([$group1->id], $oneResponse, 'data.meta.filter.group_ids');
} }
public function testItFiltersForAusstand(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
Member::factory()
->has(InvoicePosition::factory()->for(Invoice::factory()->status(InvoiceStatus::NEW)))
->defaults()->create();
Member::factory()->defaults()->create();
Member::factory()->defaults()->create();
$defaultResponse = $this->callFilter('member.index', []);
$ausstandResponse = $this->callFilter('member.index', ['ausstand' => true]);
$this->assertCount(3, $this->inertia($defaultResponse, 'data.data'));
$this->assertCount(1, $this->inertia($ausstandResponse, 'data.data'));
$this->assertInertiaHas(true, $ausstandResponse, 'data.meta.filter.ausstand');
$this->assertInertiaHas(false, $defaultResponse, 'data.meta.filter.ausstand');
}
public function testItLoadsGroups(): void public function testItLoadsGroups(): void
{ {
$this->withoutExceptionHandling(); $this->withoutExceptionHandling();
@ -201,6 +192,7 @@ class IndexTest extends TestCase
$parent2 = Group::factory()->name('par2')->create(); $parent2 = Group::factory()->name('par2')->create();
$this->withoutExceptionHandling()->login()->loginNami(12345, 'password', $parent1); $this->withoutExceptionHandling()->login()->loginNami(12345, 'password', $parent1);
sleep(1);
$response = $this->get('/member'); $response = $this->get('/member');
$response->assertOk(); $response->assertOk();

View File

@ -13,10 +13,12 @@ use Illuminate\Testing\AssertableJsonString;
use Illuminate\Testing\TestResponse; use Illuminate\Testing\TestResponse;
use Phake; use Phake;
use PHPUnit\Framework\Assert; use PHPUnit\Framework\Assert;
use Symfony\Component\HttpFoundation\File\File;
use Tests\Lib\MakesHttpCalls; use Tests\Lib\MakesHttpCalls;
use Tests\Lib\TestsInertia; use Tests\Lib\TestsInertia;
use Zoomyboy\LaravelNami\Authentication\Auth; use Zoomyboy\LaravelNami\Authentication\Auth;
use Illuminate\Support\Facades\Artisan;
use Laravel\Scout\Console\FlushCommand;
use Laravel\Scout\Console\SyncIndexSettingsCommand;
abstract class TestCase extends BaseTestCase abstract class TestCase extends BaseTestCase
{ {
@ -92,6 +94,15 @@ abstract class TestCase extends BaseTestCase
return $this; return $this;
} }
public function useMeilisearch(): self
{
config()->set('scout.driver', 'meilisearch');
Artisan::call(FlushCommand::class, ['model' => Member::class]);
Artisan::call(SyncIndexSettingsCommand::class);
return $this;
}
/** /**
* @param <class-string> $class * @param <class-string> $class
*/ */
@ -123,6 +134,16 @@ abstract class TestCase extends BaseTestCase
return $this; return $this;
}); });
TestResponse::macro('assertInertiaCount', function ($path, $count) {
/** @var TestResponse */
$response = $this;
$props = data_get($response->viewData('page'), 'props');
Assert::assertNotNull($props);
$json = new AssertableJsonString($props);
$json->assertCount($count, $path);
return $this;
});
TestResponse::macro('assertPdfPageCount', function (int $count) { TestResponse::macro('assertPdfPageCount', function (int $count) {
/** @var TestResponse */ /** @var TestResponse */
$response = $this; $response = $this;

View File

@ -6,6 +6,7 @@ use Symfony\Component\HttpFoundation\File\File;
/** /**
* @method self assertInertiaPath(string $path, string|array<string, mixed>|int|null $value) * @method self assertInertiaPath(string $path, string|array<string, mixed>|int|null $value)
* @method self assertInertiaCount(string $path, int $count)
* @method self assertPdfPageCount(int $count) * @method self assertPdfPageCount(int $count)
* @method self assertPdfName(string $filename) * @method self assertPdfName(string $filename)
* @method File getFile() * @method File getFile()