Compare commits

...

14 Commits

Author SHA1 Message Date
philipp lang bf7a298afc Add SettingIntro component
continuous-integration/drone/push Build is failing Details
2024-10-25 01:58:27 +02:00
philipp lang 7a1ab8f03b Add Nami Settings 2024-10-25 01:43:13 +02:00
philipp lang afffecb4c0 Move test files 2024-10-25 00:53:18 +02:00
philipp lang 1c7e010249 Add page title 2024-10-25 00:52:16 +02:00
philipp lang 644a70a082 Add active menu entry for setting 2024-10-25 00:48:52 +02:00
philipp lang e6d33ba2de Set active title for setting 2024-10-25 00:46:03 +02:00
philipp lang c67ae040de Update arch tests 2024-10-25 00:39:03 +02:00
philipp lang 54b413ae68 Lint 2024-10-25 00:38:35 +02:00
philipp lang d6325fbc33 Move types 2024-10-24 23:24:21 +02:00
philipp lang 3ff185e059 Move modules 2024-10-24 23:18:17 +02:00
philipp lang 0d412544bb Add Badge component 2024-10-24 23:05:12 +02:00
philipp lang 201b99cacf Add data type cast for Mailgateway 2024-10-24 22:42:30 +02:00
philipp lang bf0e9cb962 Allow MailmanTypeRequest to cast to data 2024-10-24 22:40:16 +02:00
philipp lang 3bc038c4c1 Mod setArray macro for nested values 2024-10-24 22:39:45 +02:00
44 changed files with 499 additions and 502 deletions

View File

@ -2,7 +2,7 @@
namespace App\Maildispatcher\Models; namespace App\Maildispatcher\Models;
use App\Mailgateway\Models\Mailgateway; use Modules\Mailgateway\Models\Mailgateway;
use Database\Factories\Maildispatcher\Models\MaildispatcherFactory; use Database\Factories\Maildispatcher\Models\MaildispatcherFactory;
use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;

View File

@ -4,8 +4,7 @@ namespace App\Maildispatcher\Resources;
use App\Lib\HasMeta; use App\Lib\HasMeta;
use App\Maildispatcher\Models\Maildispatcher; use App\Maildispatcher\Models\Maildispatcher;
use App\Mailgateway\Models\Mailgateway; use Modules\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Resources\MailgatewayResource;
use App\Member\FilterScope; use App\Member\FilterScope;
use App\Member\Member; use App\Member\Member;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;

View File

@ -1,27 +0,0 @@
<?php
namespace App\Mailgateway\Actions;
use App\Mailgateway\Models\Mailgateway;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
class StoreAction
{
use AsAction;
use ValidatesRequests;
/**
* @param array<string, mixed> $input
*/
public function handle(array $input): void
{
$this->checkIfWorks($input);
Mailgateway::create($input);
}
public function asController(ActionRequest $request): void
{
$this->handle($request->validated());
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace App\Mailgateway\Actions;
use App\Mailgateway\Models\Mailgateway;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
class UpdateAction
{
use AsAction;
use ValidatesRequests;
/**
* @param array<string, mixed> $input
*/
public function handle(Mailgateway $mailgateway, array $input): void
{
$this->checkIfWorks($input);
$mailgateway->update($input);
}
public function asController(Mailgateway $mailgateway, ActionRequest $request): void
{
$this->handle($mailgateway, $request->validated());
}
}

View File

@ -1,65 +0,0 @@
<?php
namespace App\Mailgateway\Actions;
use App\Mailgateway\Types\Type;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Lorisleiva\Actions\ActionRequest;
trait ValidatesRequests
{
/**
* @param array<string, mixed> $input
*/
public function checkIfWorks(array $input): void
{
if (!app(data_get($input, 'type.cls'))->setParams($input['type']['params'])->works()) {
throw ValidationException::withMessages(['connection' => 'Verbindung fehlgeschlagen.']);
}
}
/**
* @return array<string, mixed>
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'domain' => 'required|string|max:255',
...$this->typeValidation(),
'type.params' => 'present|array',
...collect(request()->input('type.cls')::rules('storeValidator'))->mapWithKeys(fn ($rules, $key) => ["type.params.{$key}" => $rules]),
];
}
/**
* @return array<string, mixed>
*/
public function getValidationAttributes(): array
{
return [
'type.cls' => 'Typ',
'name' => 'Beschreibung',
'domain' => 'Domain',
...collect(request()->input('type.cls')::fieldNames())->mapWithKeys(fn ($attribute, $key) => ["type.params.{$key}" => $attribute]),
];
}
/**
* @return array<string, mixed>
*/
private function typeValidation(): array
{
return [
'type.cls' => ['required', 'string', 'max:255', Rule::in(app('mail-gateways'))],
];
}
public function prepareForValidation(ActionRequest $request): void
{
if (!is_subclass_of(request()->input('type.cls'), Type::class)) {
throw ValidationException::withMessages(['type.cls' => 'Typ ist nicht valide.']);
}
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace App\Mailgateway\Casts;
use App\Mailgateway\Types\Type;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
/**
* @implements CastsAttributes<Type, Type>
*/
class TypeCast implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param mixed $value
* @param array<string, mixed> $attributes
*
* @return mixed
*/
public function get($model, string $key, $value, array $attributes)
{
$value = json_decode($value, true);
return app($value['cls'])->setParams($value['params']);
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param mixed $value
* @param array<string, mixed> $attributes
*
* @return mixed
*/
public function set($model, string $key, $value, array $attributes)
{
return json_encode($value);
}
}

View File

@ -1,33 +0,0 @@
<?php
namespace App\Mailgateway;
use App\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Resources\MailgatewayResource;
use App\Setting\LocalSettings;
/**
* @deprecated
*/
class MailgatewaySettings extends LocalSettings
{
public static function group(): string
{
return 'mailgateway';
}
public static function title(): string
{
return 'E-Mail-Verbindungen';
}
/**
* @inheritdoc
*/
public function viewData(): array
{
return [
'data' => MailgatewayResource::collection(Mailgateway::paginate(10)),
];
}
}

View File

@ -1,69 +0,0 @@
<?php
namespace App\Mailgateway\Resources;
use App\Lib\HasMeta;
use App\Mailgateway\Models\Mailgateway;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin Mailgateway
* @deprecated
*/
class MailgatewayResource extends JsonResource
{
use HasMeta;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
*
* @return array<string, mixed>
*/
public function toArray($request)
{
return [
'name' => $this->name,
'domain' => $this->domain,
'type_human' => $this->type::name(),
'works' => $this->type->works(),
'type' => $this->type->toResource(),
'id' => $this->id,
'links' => [
'update' => route('mailgateway.update', ['mailgateway' => $this->getModel()]),
],
];
}
/**
* @return array<string, mixed>
*/
public static function meta(): array
{
return [
'links' => [
'store' => route('mailgateway.store'),
],
'types' => app('mail-gateways')->map(fn ($gateway) => [
'id' => $gateway,
'name' => $gateway::name(),
'fields' => $gateway::presentFields('storeValidator'),
'defaults' => (object) $gateway::defaults(),
])->prepend([
'id' => null,
'name' => '-- kein --',
'fields' => [],
'defaults' => (object) [],
]),
'default' => [
'domain' => '',
'name' => '',
'type' => [
'params' => [],
'cls' => null,
],
],
];
}
}

View File

@ -3,13 +3,10 @@
namespace App\Providers; namespace App\Providers;
use App\Form\Models\Form; use App\Form\Models\Form;
use App\Mailgateway\Types\LocalType;
use App\Mailgateway\Types\MailmanType;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Laravel\Telescope\Telescope;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -30,11 +27,6 @@ class AppServiceProvider extends ServiceProvider
return $this; return $this;
}); });
app()->bind('mail-gateways', fn () => collect([
LocalType::class,
MailmanType::class,
]));
app()->extend('media-library-helpers', fn ($p) => $p->put('form', Form::class)); app()->extend('media-library-helpers', fn ($p) => $p->put('form', Form::class));
Blade::componentNamespace('App\\View\\Mail', 'mail-view'); Blade::componentNamespace('App\\View\\Mail', 'mail-view');

View File

@ -3,14 +3,10 @@
namespace App\Setting; namespace App\Setting;
use App\Group; use App\Group;
use App\Initialize\Actions\NamiLoginCheckAction;
use App\Nami\Actions\SettingSaveAction;
use App\Setting\Contracts\Storeable;
use Lorisleiva\Actions\ActionRequest;
use Zoomyboy\LaravelNami\Api; use Zoomyboy\LaravelNami\Api;
use Zoomyboy\LaravelNami\Nami; use Zoomyboy\LaravelNami\Nami;
class NamiSettings extends LocalSettings implements Storeable class NamiSettings extends LocalSettings
{ {
public int $mglnr; public int $mglnr;
@ -43,14 +39,6 @@ class NamiSettings extends LocalSettings implements Storeable
]; ];
} }
public function beforeSave(ActionRequest $request): void
{
NamiLoginCheckAction::run([
'mglnr' => $request->mglnr,
'password' => $request->password,
]);
}
public function localGroup(): ?Group public function localGroup(): ?Group
{ {
return Group::firstWhere('nami_id', $this->default_group_id); return Group::firstWhere('nami_id', $this->default_group_id);

View File

@ -7,7 +7,7 @@ use Illuminate\View\Component;
class Header extends Component class Header extends Component
{ {
public function __construct(public string $title, public bool $closeable = false) public function __construct(public string $title)
{ {
} }
@ -15,17 +15,12 @@ class Header extends Component
{ {
return <<<'HTML' return <<<'HTML'
<div class="h-16 px-6 flex items-center justify-between border-b border-solid border-gray-600 group-[.is-bright]:border-gray-500"> <div class="h-16 px-6 flex items-center justify-between border-b border-solid border-gray-600 group-[.is-bright]:border-gray-500">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-4">
{{ $beforeTitle ?? ''}} {{ $beforeTitle ?? ''}}
<span class="text-sm md:text-xl font-semibold leading-none text-white">{{ $title }}</span> <span class="text-sm md:text-xl font-semibold leading-none text-white">{{ $title }}</span>
{{ $toolbar ?? '' }} {{ $toolbar ?? '' }}
</div> </div>
<div class="flex items-center space-x-4 ml-2"> <div class="flex items-center space-x-4 ml-2">
@if ($closeable)
<a href="#" class="btn label btn-primary-light icon" wire:click="close">
<ui-sprite class="w-3 h-3" src="close"></ui-sprite>
</a>
@endif
{{ $right ?? '' }} {{ $right ?? '' }}
</div> </div>
</div> </div>

View File

@ -7,8 +7,9 @@ use Illuminate\View\Component;
class Layout extends Component class Layout extends Component
{ {
public function __construct(public string $pageClass = '') public function __construct(public string $pageClass = '', public string $title = '')
{ {
session()->put('title', $title);
} }
public function userName(): string public function userName(): string
@ -26,9 +27,9 @@ class Layout extends Component
return <<<'HTML' return <<<'HTML'
<div class="grow flex flex-col" @refresh-page.window="$wire.$refresh"> <div class="grow flex flex-col" @refresh-page.window="$wire.$refresh">
<div class="grow bg-gray-900 flex flex-col duration-300 navbar:ml-60"> <div class="grow bg-gray-900 flex flex-col duration-300 navbar:ml-60">
<x-page::header title="{{ session()->get('title') }}"> <x-page::header :title="$title">
<x-slot:beforeTitle> <x-slot:beforeTitle>
<a href="#" class="mr-2 lg:hidden" wire:click.prevent="dispatch('toggle-sidebar')"> <a href="#" class="lg:hidden" wire:click.prevent="dispatch('toggle-sidebar')">
<x-ui::sprite src="menu" class="text-gray-100 w-5 h-5"></x-ui::sprite> <x-ui::sprite src="menu" class="text-gray-100 w-5 h-5"></x-ui::sprite>
</a> </a>
</x-slot:beforeTitle> </x-slot:beforeTitle>

View File

@ -18,15 +18,19 @@ class SettingLayout extends Component
'is_active' => get_class($setting) === $active, 'is_active' => get_class($setting) === $active,
'title' => $setting->title(), 'title' => $setting->title(),
])->toArray(); ])->toArray();
session()->put('menu', 'setting');
} }
public function render() public function render()
{ {
return <<<'HTML' return <<<'HTML'
<x-page::layout> <x-page::layout :title="$active::title()">
<x-slot:right> <x-slot:right>
{{ $right ?? '' }} {{ $right ?? '' }}
</x-slot:right> </x-slot:right>
<x-slot:toolbar>
{{ $toolbar ?? '' }}
</x-slot:toolbar>
<div class="flex grow relative"> <div class="flex grow relative">
<x-ui::menulist :entries="$entries"></x-ui::menulist> <x-ui::menulist :entries="$entries"></x-ui::menulist>
<div class="grow"> <div class="grow">

33
app/View/Ui/Badge.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App\View\Ui;
use App\View\Traits\HasColors;
use Illuminate\View\Component;
class Badge extends Component
{
use HasColors;
public function __construct(
public string $icon,
public string $variant = 'primary'
) {
}
public function render()
{
return <<<'HTML'
<button type="button" href="#" {{ $attributes }} class="h-6 px-3 space-x-2 items-center rounded-full {{ $allColors($variant) }} hidden lg:flex">
<x-ui::sprite class="w-3 h-3 flex-none" :src="$icon"></x-ui::sprite>
<span class="text-sm">
{{$slot}}
</span>
</button>
<button type="button" x-tooltip.raw="{{$slot}}" href="#" {{ $attributes }} class="h-6 px-3 space-x-2 flex items-center rounded-full {{ $allColors($variant) }} lg:hidden">
<x-ui::sprite class="w-3 h-3 flex-none" :src="$icon"></x-ui::sprite>
</button>
HTML;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\View\Ui;
use Illuminate\View\Component;
class SettingIntro extends Component
{
public function __construct(public ?string $title = null)
{
}
public function render()
{
return <<<'HTML'
<div {{$attributes}}>
@if ($title) <h2 class="text-lg font-semibold text-gray-300">{{$title}}</h2> @endif
<div class="text-gray-100 text-sm">
{{ $slot }}
</div>
</div>
HTML;
}
}

View File

@ -86,7 +86,8 @@
"mockery/mockery": "^1.4.4", "mockery/mockery": "^1.4.4",
"orchestra/testbench": "^9.0", "orchestra/testbench": "^9.0",
"pestphp/pest": "^3.0", "pestphp/pest": "^3.0",
"phpstan/phpstan-mockery": "^1.1" "phpstan/phpstan-mockery": "^1.1",
"qossmic/deptrac": "^2.0"
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,
@ -121,6 +122,10 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"prefer-stable": true, "prefer-stable": true,
"scripts": { "scripts": {
"archtest": [
"./vendor/bin/deptrac analyze",
"./vendor/bin/pest tests/Arch.php"
],
"post-autoload-dump": [ "post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi" "@php artisan package:discover --ansi"

47
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "67ae3a3987355f098ee6f850c6f8e846", "content-hash": "3ffa3d4a189ba5d296ca71166298a74f",
"packages": [ "packages": [
{ {
"name": "amphp/amp", "name": "amphp/amp",
@ -15743,6 +15743,51 @@
}, },
"time": "2024-09-11T15:47:29+00:00" "time": "2024-09-11T15:47:29+00:00"
}, },
{
"name": "qossmic/deptrac",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/qossmic/deptrac.git",
"reference": "f646695c1468051138e1347d89590f9084fa6256"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/qossmic/deptrac/zipball/f646695c1468051138e1347d89590f9084fa6256",
"reference": "f646695c1468051138e1347d89590f9084fa6256",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^8.1"
},
"suggest": {
"ext-dom": "For using the JUnit output formatter"
},
"bin": [
"bin/deptrac",
"deptrac.phar"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Qossmic\\Deptrac\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Deptrac is a static code analysis tool that helps to enforce rules for dependencies between software layers.",
"support": {
"issues": "https://github.com/qossmic/deptrac/issues",
"source": "https://github.com/qossmic/deptrac/tree/2.0.1"
},
"time": "2024-06-17T10:43:11+00:00"
},
{ {
"name": "rector/rector", "name": "rector/rector",
"version": "1.2.5", "version": "1.2.5",

View File

@ -182,6 +182,7 @@ return [
Modules\Module\ModuleServiceProvider::class, Modules\Module\ModuleServiceProvider::class,
Modules\Invoice\InvoiceServiceProvider::class, Modules\Invoice\InvoiceServiceProvider::class,
Modules\Mailgateway\MailgatewayServiceProvider::class, Modules\Mailgateway\MailgatewayServiceProvider::class,
Modules\Nami\NamiServiceProvider::class,
], ],
/* /*

30
deptrac.yaml Normal file
View File

@ -0,0 +1,30 @@
deptrac:
paths:
- ./app
- ./modules
exclude_files:
- '#.*Test.php$#'
layers:
- name: AppFiles
collectors:
- type: bool
must:
- type: classLike
value: .*
- type: directory
value: app/.*
- name: ModuleFiles
collectors:
- type: bool
must:
- type: classLike
value: .*
- type: directory
value: modules/.*
ruleset:
ModuleFiles:
- AppFiles
AppFiles:
- ModuleFiles

View File

@ -35,8 +35,9 @@ class SettingView extends Component
<form id="billsettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" wire:submit.prevent="save"> <form id="billsettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" wire:submit.prevent="save">
<x-form::text name="from" wire:model="settings.from" label="Absender" hint="Absender-Name in Kurzform, i.d.R. der kurze Stammesname"></x-form::text> <x-form::text name="from" wire:model="settings.from" label="Absender" hint="Absender-Name in Kurzform, i.d.R. der kurze Stammesname"></x-form::text>
<x-form::text name="from_long" wire:model="settings.from_long" label="Absender (lang)" hint="Absender-Name in Langform, i.d.R. der Stammesname"></x-form::text> <x-form::text name="from_long" wire:model="settings.from_long" label="Absender (lang)" hint="Absender-Name in Langform, i.d.R. der Stammesname"></x-form::text>
<h2 class="text-lg font-semibold text-gray-300 col-span-2 mt-5">Kontaktdaten</h2> <x-ui::setting-intro class="col-span-full mt-5" title="Kontaktdaten">
<div class="col-span-2 text-gray-300 text-sm">Diese Kontaktdaten stehen im Absender-Bereich auf der Rechnung.</div> Hier kannst du deine Zugangsdaten zu NaMi anpassen, falls sich z.B. dein Passwort geändert hat.
</x-ui::setting-intro>
<x-form::text name="address" wire:model="settings.address" label="Straße"></x-form::text> <x-form::text name="address" wire:model="settings.address" label="Straße"></x-form::text>
<x-form::text name="zip" wire:model="settings.zip" label="PLZ"></x-form::text> <x-form::text name="zip" wire:model="settings.zip" label="PLZ"></x-form::text>
<x-form::text name="place" wire:model="settings.place" label="Ort"></x-form::text> <x-form::text name="place" wire:model="settings.place" label="Ort"></x-form::text>

View File

@ -2,9 +2,9 @@
namespace Modules\Mailgateway\Components; namespace Modules\Mailgateway\Components;
use App\Mailgateway\Models\Mailgateway; use Modules\Mailgateway\Models\Mailgateway;
use Modules\Mailgateway\Types\Type;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Livewire\Attributes\On; use Livewire\Attributes\On;
use Livewire\Attributes\Validate; use Livewire\Attributes\Validate;
@ -15,9 +15,9 @@ class Form extends Component
public string $name = ''; public string $name = '';
public string $domain = ''; public string $domain = '';
public array $params = []; public ?Type $type = null;
#[Validate('required')] #[Validate('required|string')]
public ?string $cls = null; public ?string $typeClass = null;
public Collection $types; public Collection $types;
public ?Mailgateway $model = null; public ?Mailgateway $model = null;
@ -26,19 +26,17 @@ class Form extends Component
return [ return [
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'domain' => 'required|string|max:255', 'domain' => 'required|string|max:255',
'cls' => ['required', 'string', 'max:255', Rule::in(app('mail-gateways'))], ...$this->type ? collect($this->type::rules())->mapWithKeys(fn ($rules, $key) => ["type.{$key}" => $rules]) : [],
'params' => 'present|array',
...$this->cls ? collect($this->cls::rules())->mapWithKeys(fn ($rules, $key) => ["params.{$key}" => $rules]) : [],
]; ];
} }
public function validationAttributes(): array public function validationAttributes(): array
{ {
return [ return [
'cls' => 'Typ', 'type' => 'Typ',
'name' => 'Beschreibung', 'name' => 'Beschreibung',
'domain' => 'Domain', 'domain' => 'Domain',
...$this->cls ? collect($this->cls::fieldNames())->mapWithKeys(fn ($attribute, $key) => ["params.{$key}" => $attribute]) : [], ...$this->type ? collect($this->type::fieldNames())->mapWithKeys(fn ($attribute, $key) => ["params.{$key}" => $attribute]) : [],
]; ];
} }
@ -53,19 +51,23 @@ class Form extends Component
$this->model = Mailgateway::find($id); $this->model = Mailgateway::find($id);
$this->name = $this->model->name; $this->name = $this->model->name;
$this->domain = $this->model->domain; $this->domain = $this->model->domain;
$this->cls = get_class($this->model->type); $this->type = $this->model->type;
$this->params = (array) $this->model->type; $this->typeClass = get_class($this->model->type);
} }
} }
public function updatedType(string $type): void
{
$this->params = $type::defaults();
}
public function fields(): array public function fields(): array
{ {
return $this->cls ? $this->cls::fields() : []; return $this->type ? $this->type::fields() : [];
}
public function updatedTypeClass(?string $type): void
{
if (!$type) {
return;
}
$this->type = $type::from([]);
} }
#[On('onStoreFromModal')] #[On('onStoreFromModal')]
@ -73,14 +75,14 @@ class Form extends Component
{ {
$this->validate(); $this->validate();
if (!app($this->cls)->setParams($this->params)->works()) { if (!$this->type->works()) {
throw ValidationException::withMessages(['connection' => 'Verbindung fehlgeschlagen.']); throw ValidationException::withMessages(['connection' => 'Verbindung fehlgeschlagen.']);
} }
$payload = [ $payload = [
'name' => $this->name, 'name' => $this->name,
'domain' => $this->domain, 'domain' => $this->domain,
'type' => ['cls' => $this->cls, 'params' => $this->params], 'type' => $this->type,
]; ];
if ($this->model) { if ($this->model) {
$this->model->update($payload); $this->model->update($payload);
@ -99,11 +101,11 @@ class Form extends Component
<form class="grid grid-cols-2 gap-3"> <form class="grid grid-cols-2 gap-3">
<x-form::text name="name" wire:model="name" label="Beschreibung" required /> <x-form::text name="name" wire:model="name" label="Beschreibung" required />
<x-form::text name="domain" wire:model="domain" label="Domain" required /> <x-form::text name="domain" wire:model="domain" label="Domain" required />
<x-form::select name="cls" wire:model.live="cls" label="Typ" :options="$types" required /> <x-form::select name="typeClass" wire:model.live="typeClass" label="Typ" :options="$types" required />
@foreach($this->fields() as $index => $field) @foreach($this->fields() as $index => $field)
<x-form::text <x-form::text
wire:key="index" wire:key="index"
wire:model="params.{{$field['name']}}" wire:model="type.{{$field['name']}}"
:label="$field['label']" :label="$field['label']"
:type="$field['type']" :type="$field['type']"
:name="$field['name']" :name="$field['name']"

View File

@ -2,7 +2,7 @@
namespace Modules\Mailgateway\Components; namespace Modules\Mailgateway\Components;
use App\Mailgateway\Models\Mailgateway; use Modules\Mailgateway\Models\Mailgateway;
use Livewire\Component; use Livewire\Component;
use Modules\Mailgateway\MailgatewaySettings; use Modules\Mailgateway\MailgatewaySettings;

View File

@ -1,4 +1,7 @@
<x-page::setting-layout :active="$settingClass"> <x-page::setting-layout :active="$settingClass">
<x-slot:toolbar>
<x-ui::badge wire:click.prevent="$dispatch('openModal', {component: 'modules.mailgateway.components.form', props: {}, title: 'Verbindung erstellen'})" icon="plus">Neue Verbindung</x-ui::badge>
</x-slot:toolbar>
<div> <div>
<x-ui::table> <x-ui::table>
<thead> <thead>
@ -9,7 +12,6 @@
<th>Aktion</th> <th>Aktion</th>
</thead> </thead>
<x-ui::action wire:click.prevent="$dispatch('openModal', {component: 'modules.mailgateway.components.form', props: {}, title: 'Verbindung erstellen'})" icon="plus" variant="danger">Neu</x-ui::action>
@foreach ($data as $index => $gateway) @foreach ($data as $index => $gateway)
<tr wire:key="$index"> <tr wire:key="$index">

View File

@ -7,6 +7,8 @@ use Illuminate\Routing\Router;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Modules\Mailgateway\Components\SettingView; use Modules\Mailgateway\Components\SettingView;
use Modules\Mailgateway\Types\LocalType;
use Modules\Mailgateway\Types\MailmanType;
class MailgatewayServiceProvider extends ServiceProvider class MailgatewayServiceProvider extends ServiceProvider
{ {
@ -32,6 +34,11 @@ class MailgatewayServiceProvider extends ServiceProvider
$router->get('/setting/mailgateway', SettingView::class)->name('setting.mailgateway'); $router->get('/setting/mailgateway', SettingView::class)->name('setting.mailgateway');
}); });
app()->bind('mail-gateways', fn () => collect([
LocalType::class,
MailmanType::class,
]));
View::addNamespace('mailgateway', __DIR__ . '/Components'); View::addNamespace('mailgateway', __DIR__ . '/Components');
} }
} }

View File

@ -1,19 +1,21 @@
<?php <?php
namespace App\Mailgateway\Models; namespace Modules\Mailgateway\Models;
use App\Mailgateway\Casts\TypeCast; use Modules\Mailgateway\Types\Type;
use Database\Factories\Mailgateway\Models\MailgatewayFactory;
use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class Mailgateway extends Model class Mailgateway extends Model
{ {
public static $factory = MailgatewayFactory::class;
/** @use HasFactory<MailgatewayFactory> */ /** @use HasFactory<MailgatewayFactory> */
use HasFactory; use HasFactory;
use HasUuids; use HasUuids;
public $casts = ['type' => TypeCast::class]; public $casts = ['type' => Type::class];
public $guarded = []; public $guarded = [];
} }

View File

@ -1,13 +1,13 @@
<?php <?php
namespace Database\Factories\Mailgateway\Models; namespace Modules\Mailgateway\Models;
use App\Mailgateway\Models\Mailgateway; use Modules\Mailgateway\Types\Type;
use App\Mailgateway\Types\Type; use Modules\Mailgateway\Types\LocalType;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Mailgateway\Models\Mailgateway> * @extends Factory<Mailgateway>
*/ */
class MailgatewayFactory extends Factory class MailgatewayFactory extends Factory
{ {
@ -22,24 +22,14 @@ class MailgatewayFactory extends Factory
{ {
return [ return [
'name' => $this->faker->words(5, true), 'name' => $this->faker->words(5, true),
'type' => [ 'type' => new LocalType(),
'cls' => app('mail-gateways')->random(),
'params' => [],
],
'domain' => $this->faker->safeEmailDomain(), 'domain' => $this->faker->safeEmailDomain(),
]; ];
} }
/** public function type(Type $type): self
* @param class-string<Type> $type
* @param array<string, mixed> $params
*/
public function type(string $type, array $params): self
{ {
return $this->state(['type' => [ return $this->state(['type' => $type]);
'cls' => $type,
'params' => $params,
]]);
} }
public function name(string $name): self public function name(string $name): self

View File

@ -1,10 +1,9 @@
<?php <?php
namespace Tests\Feature\Mailgateway; namespace Modules\Mailgateway\Tests;
use App\Mailgateway\Models\Mailgateway; use Modules\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Types\LocalType; use Modules\Mailgateway\Types\LocalType;
use App\Mailgateway\Types\MailmanType;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Livewire\Livewire; use Livewire\Livewire;
use Modules\Mailgateway\Components\SettingView; use Modules\Mailgateway\Components\SettingView;
@ -21,7 +20,7 @@ it('test it can view index page', function () {
it('test it displays local gateways', function () { it('test it displays local gateways', function () {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
Mailgateway::factory()->type(LocalType::class, [])->name('Lore')->domain('example.com')->create(); Mailgateway::factory()->type(LocalType::from([]))->name('Lore')->domain('example.com')->create();
Livewire::test(SettingView::class) Livewire::test(SettingView::class)
->assertSeeHtml('example.com') ->assertSeeHtml('example.com')
@ -32,8 +31,8 @@ it('test it displays local gateways', function () {
it('displays mailman gateways', function () { it('displays mailman gateways', function () {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
$typeParams = MailmanTypeRequest::new()->succeeds()->create(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']); $typeParams = MailmanTypeRequest::new()->succeeds()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']);
Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(); Mailgateway::factory()->type($typeParams->toData())->create();
Livewire::test(SettingView::class)->assertSeeHtml('Verbindung erfolgreich'); Livewire::test(SettingView::class)->assertSeeHtml('Verbindung erfolgreich');
}); });

View File

@ -1,12 +1,12 @@
<?php <?php
namespace Tests\Feature\Mailgateway; namespace Modules\Mailgateway\Tests;
use App\Mailgateway\Types\LocalType;
use App\Mailgateway\Types\MailmanType;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Livewire\Livewire; use Livewire\Livewire;
use Modules\Mailgateway\Components\Form; use Modules\Mailgateway\Components\Form;
use Modules\Mailgateway\Types\LocalType;
use Modules\Mailgateway\Types\MailmanType;
use Tests\RequestFactories\MailmanTypeRequest; use Tests\RequestFactories\MailmanTypeRequest;
use Tests\TestCase; use Tests\TestCase;
@ -17,39 +17,32 @@ it('test it saves a mail gateway', function () {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
Livewire::test(Form::class) Livewire::test(Form::class)
->set('typeClass', LocalType::class)
->set('name', 'lala') ->set('name', 'lala')
->set('domain', 'example.com') ->set('domain', 'example.com')
->set('cls', LocalType::class)
->call('onSave') ->call('onSave')
->assertHasNoErrors()
->assertDispatched('closeModal') ->assertDispatched('closeModal')
->assertDispatched('refresh-page') ->assertDispatched('refresh-page')
->assertDispatched('success'); ->assertDispatched('success');
$this->assertDatabaseHas('mailgateways', [ test()->assertDatabaseHas('mailgateways', [
'domain' => 'example.com', 'domain' => 'example.com',
'name' => 'lala', 'name' => 'lala',
'type' => json_encode([ 'type' => json_encode([
'cls' => LocalType::class, 'type' => LocalType::class,
'params' => [], 'data' => [],
]), ]),
]); ]);
}); });
it('validates type', function () {
test()->withoutExceptionHandling()->login()->loginNami();
Livewire::test(Form::class)
->set('cls', '')
->assertHasErrors(['cls' => 'required']);
});
it('test it validates mail gateway', function (array $attributes, array $errors) { it('test it validates mail gateway', function (array $attributes, array $errors) {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
Livewire::test(Form::class) Livewire::test(Form::class)
->set('name', 'lala') ->set('name', 'lala')
->set('domain', 'example.com') ->set('domain', 'example.com')
->set('cls', LocalType::class) ->set('typeClass', LocalType::class)
->setArray($attributes) ->setArray($attributes)
->call('onSave') ->call('onSave')
->assertHasErrors($errors) ->assertHasErrors($errors)
@ -59,6 +52,7 @@ it('test it validates mail gateway', function (array $attributes, array $errors)
})->with([ })->with([
[['name' => ''], ['name' => 'required']], [['name' => ''], ['name' => 'required']],
[['domain' => ''], ['domain' => 'required']], [['domain' => ''], ['domain' => 'required']],
[['typeClass' => ''], ['typeClass' => 'required']],
]); ]);
it('test it validates mailman type', function (array $attributes, array $errors) { it('test it validates mailman type', function (array $attributes, array $errors) {
@ -67,20 +61,20 @@ it('test it validates mailman type', function (array $attributes, array $errors)
Livewire::test(Form::class) Livewire::test(Form::class)
->set('name', 'lala') ->set('name', 'lala')
->set('domain', 'example.com') ->set('domain', 'example.com')
->set('cls', MailmanType::class) ->set('typeClass', MailmanType::class)
->set('params.url', 'exampl.com') ->set('type.url', 'exampl.com')
->set('params.user', '::user::') ->set('type.user', '::user::')
->set('params.password', 'password') ->set('type.password', 'password')
->setArray($attributes) ->setArray($attributes)
->call('onSave') ->call('onSave')
->assertHasErrors($errors) ->assertHasErrors($errors)
->assertNotDispatched('closeModal'); ->assertNotDispatched('closeModal');
})->with([ })->with([
[['params.url' => ''], ['params.url' => 'required']], [['type.url' => ''], ['type.url' => 'required']],
[['params.user' => ''], ['params.user' => 'required']], [['type.user' => ''], ['type.user' => 'required']],
[['params.password' => ''], ['params.password' => 'required']], [['type.password' => ''], ['type.password' => 'required']],
[['params.owner' => ''], ['params.owner' => 'required']], [['type.owner' => ''], ['type.owner' => 'required']],
[['params.owner' => 'aaa'], ['params.owner' => 'email']], [['type.owner' => 'aaa'], ['type.owner' => 'email']],
]); ]);
it('test it stores mailman gateway', function () { it('test it stores mailman gateway', function () {
@ -92,16 +86,16 @@ it('test it stores mailman gateway', function () {
->setArray([ ->setArray([
'name' => 'lala', 'name' => 'lala',
'domain' => 'https://example.com', 'domain' => 'https://example.com',
'cls' => MailmanType::class, 'typeClass' => MailmanType::class,
'params' => $typeParams
]) ])
->setArray('type', $typeParams)
->call('onSave') ->call('onSave')
->assertDispatched('closeModal'); ->assertDispatched('closeModal');
$this->assertDatabaseHas('mailgateways', [ test()->assertDatabaseHas('mailgateways', [
'type' => json_encode([ 'type' => json_encode([
'cls' => MailmanType::class, 'type' => MailmanType::class,
'params' => $typeParams, 'data' => $typeParams,
]), ]),
'name' => 'lala', 'name' => 'lala',
'domain' => 'https://example.com', 'domain' => 'https://example.com',
@ -117,12 +111,12 @@ it('test it checks mailman connection', function () {
->setArray([ ->setArray([
'name' => 'lala', 'name' => 'lala',
'domain' => 'https://example.com', 'domain' => 'https://example.com',
'cls' => MailmanType::class, 'typeClass' => MailmanType::class,
'params' => $typeParams
]) ])
->setArray('type', $typeParams)
->call('onSave') ->call('onSave')
->assertHasErrors('connection') ->assertHasErrors('connection')
->assertNotDispatched('closeModal'); ->assertNotDispatched('closeModal');
$this->assertDatabaseCount('mailgateways', 0); test()->assertDatabaseCount('mailgateways', 0);
}); });

View File

@ -1,10 +1,10 @@
<?php <?php
namespace Tests\Feature\Mailgateway; namespace Modules\Mailgateway\Tests;
use App\Mailgateway\Models\Mailgateway; use Modules\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Types\LocalType; use Modules\Mailgateway\Types\LocalType;
use App\Mailgateway\Types\MailmanType; use Modules\Mailgateway\Types\MailmanType;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Livewire\Livewire; use Livewire\Livewire;
use Modules\Mailgateway\Components\Form; use Modules\Mailgateway\Components\Form;
@ -17,47 +17,52 @@ uses(TestCase::class);
it('test it sets attributes for mailman', function () { it('test it sets attributes for mailman', function () {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
$typeParams = MailmanTypeRequest::new()->create(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']); $typeParams = MailmanTypeRequest::new()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']);
$mailgateway = Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(['name' => '::name::', 'domain' => 'example.com']); $mailgateway = Mailgateway::factory()->type($typeParams->toData())->create(['name' => '::name::', 'domain' => 'example.com']);
Livewire::test(Form::class, ['id' => $mailgateway->id]) Livewire::test(Form::class, ['id' => $mailgateway->id])
->assertSet('model', fn ($m) => $m->is($mailgateway)) ->assertSet('model', fn ($m) => $m->is($mailgateway))
->assertSet('name', '::name::') ->assertSet('name', '::name::')
->assertSet('domain', 'example.com') ->assertSet('domain', 'example.com')
->assertSet('cls', MailmanType::class) ->assertSet(
->assertSet('params.url', 'https://mailman.example.com') 'type',
->assertSet('params.user', 'user') fn ($type) => $type instanceof MailmanType
->assertSet('params.password', 'password') && $type->url === 'https://mailman.example.com'
->assertSet('params.owner', 'owner'); && $type->user === 'user' &&
$type->password === 'password'
&& $type->owner === 'owner'
);
}); });
it('test it sets attributes for local', function () { it('test it sets attributes for local', function () {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
$mailgateway = Mailgateway::factory()->type(LocalType::class, [])->create(['name' => '::name::', 'domain' => 'example.com']); $mailgateway = Mailgateway::factory()->create(['name' => '::name::', 'domain' => 'example.com']);
Livewire::test(Form::class, ['id' => $mailgateway->id]) Livewire::test(Form::class, ['id' => $mailgateway->id])
->assertSet('name', '::name::') ->assertSet('name', '::name::')
->assertSet('domain', 'example.com') ->assertSet('domain', 'example.com')
->assertSet('cls', LocalType::class) ->assertSet('type', fn ($type) => $type instanceof LocalType);
->assertSet('params', []);
}); });
it('test it validates type', function () { it('test it validates type', function () {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
$mailgateway = Mailgateway::factory()->type(LocalType::class, [])->create(['name' => '::name::', 'domain' => 'example.com']); $mailgateway = Mailgateway::factory()->create(['name' => '::name::', 'domain' => 'example.com']);
Livewire::test(Form::class, ['id' => $mailgateway->id]) Livewire::test(Form::class, ['id' => $mailgateway->id])
->set('cls', '') ->set('typeClass', '')
->assertHasErrors(['cls' => 'required']); ->assertHasErrors(['typeClass' => 'required']);
}); });
it('test it updates a mailman gateway without updating password', function () { it('test it updates a mailman gateway without updating password', function () {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
$typeParams = MailmanTypeRequest::new()->succeeds()->create(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com']); $typeParams = MailmanTypeRequest::new()
$mailgateway = Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(['name' => '::name::', 'domain' => 'example.com']); ->succeeds()
->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com'])
->toData();
$mailgateway = Mailgateway::factory()->type($typeParams)->create(['name' => '::name::', 'domain' => 'example.com']);
Livewire::test(Form::class, ['id' => $mailgateway->id]) Livewire::test(Form::class, ['id' => $mailgateway->id])
->set('name', '::newname::') ->set('name', '::newname::')
@ -70,37 +75,37 @@ it('test it updates a mailman gateway without updating password', function () {
$this->assertDatabaseCount('mailgateways', 1); $this->assertDatabaseCount('mailgateways', 1);
$this->assertDatabaseHas('mailgateways', [ $this->assertDatabaseHas('mailgateways', [
'name' => '::newname::', 'name' => '::newname::',
'type' => json_encode(['cls' => MailmanType::class, 'params' => $typeParams]), 'type' => json_encode(['type' => MailmanType::class, 'data' => $typeParams]),
]); ]);
}); });
it('test it updates a mailman gateway with password', function () { it('test it updates a mailman gateway with password', function () {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
$typeParams = MailmanTypeRequest::new()->create(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com']); $typeParams = MailmanTypeRequest::new()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com']);
$newTypeParams = MailmanTypeRequest::new()->succeeds()->create(['url' => 'https://mailman.example.com', 'user' => 'newuser', 'password' => 'password', 'owner' => 'owner@example.com']); $newTypeParams = MailmanTypeRequest::new()->succeeds()->create(['url' => 'https://mailman.example.com', 'user' => 'newuser', 'password' => 'password', 'owner' => 'owner@example.com']);
$mailgateway = Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(); $mailgateway = Mailgateway::factory()->type($typeParams->toData())->create();
Livewire::test(Form::class, ['id' => $mailgateway->id]) Livewire::test(Form::class, ['id' => $mailgateway->id])
->set('params.user', 'newuser') ->set('type.user', 'newuser')
->call('onSave') ->call('onSave')
->assertHasNoErrors(); ->assertHasNoErrors();
$this->assertDatabaseCount('mailgateways', 1); $this->assertDatabaseCount('mailgateways', 1);
$this->assertDatabaseHas('mailgateways', [ $this->assertDatabaseHas('mailgateways', [
'type' => json_encode(['cls' => MailmanType::class, 'params' => $newTypeParams]), 'type' => json_encode(['type' => MailmanType::class, 'data' => $newTypeParams]),
]); ]);
}); });
it('test it checks mailgateway connection when updating', function () { it('test it checks mailgateway connection when updating', function () {
test()->withoutExceptionHandling()->login()->loginNami(); test()->withoutExceptionHandling()->login()->loginNami();
$typeParams = MailmanTypeRequest::new()->create(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com']); $typeParams = MailmanTypeRequest::new()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com']);
MailmanTypeRequest::new()->fails()->create(['url' => 'https://mailman.example.com', 'user' => 'newuser', 'password' => 'password', 'owner' => 'owner@example.com']); MailmanTypeRequest::new()->fails()->create(['url' => 'https://mailman.example.com', 'user' => 'newuser', 'password' => 'password', 'owner' => 'owner@example.com']);
$mailgateway = Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(); $mailgateway = Mailgateway::factory()->type($typeParams->toData())->create();
Livewire::test(Form::class, ['id' => $mailgateway->id]) Livewire::test(Form::class, ['id' => $mailgateway->id])
->set('params.user', 'newuser') ->set('type.user', 'newuser')
->call('onSave') ->call('onSave')
->assertHasErrors('connection'); ->assertHasErrors('connection');
}); });

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Mailgateway\Types; namespace Modules\Mailgateway\Types;
use App\Maildispatcher\Data\MailEntry; use App\Maildispatcher\Data\MailEntry;
use App\Maildispatcher\Models\Localmaildispatcher; use App\Maildispatcher\Models\Localmaildispatcher;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Mailgateway\Types; namespace Modules\Mailgateway\Types;
use App\Maildispatcher\Data\MailEntry; use App\Maildispatcher\Data\MailEntry;
use App\Mailman\Data\MailingList; use App\Mailman\Data\MailingList;

View File

@ -1,12 +1,18 @@
<?php <?php
namespace App\Mailgateway\Types; namespace Modules\Mailgateway\Types;
use App\Maildispatcher\Data\MailEntry; use App\Maildispatcher\Data\MailEntry;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Wireable;
use Spatie\LaravelData\Concerns\WireableData;
use Spatie\LaravelData\Data;
abstract class Type abstract class Type extends Data implements Wireable
{ {
use WireableData;
abstract public static function name(): string; abstract public static function name(): string;
/** /**
@ -67,17 +73,6 @@ abstract class Type
])->toArray(); ])->toArray();
} }
/**
* @return array<string, array<string, mixed>>
*/
public function toResource(): array
{
return [
'cls' => get_class($this),
'params' => get_object_vars($this),
];
}
/** /**
* @param Collection<int, MailEntry> $results * @param Collection<int, MailEntry> $results
*/ */

View File

@ -43,10 +43,9 @@ class SettingView extends Component
</x-slot:right> </x-slot:right>
<form id="modulesettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" <form id="modulesettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start"
wire:submit.prevent="save"> wire:submit.prevent="save">
<div class="col-span-full text-gray-100 mb-3"> <x-ui::setting-intro class="col-span-full">
<p class="text-sm">Hier kannst du Funktionen innerhalb von Adrema (Module) aktivieren oder deaktivieren Hier kannst du Funktionen innerhalb von Adrema (Module) aktivieren oder deaktivieren und so den Funktionsumfang auf deine Bedürfnisse anpassen.
und so den Funktionsumfang auf deine Bedürfnisse anpassen.</p> </x-ui::setting-intro>
</div>
@foreach ($all as $module) @foreach ($all as $module)
<x-form::lever wire:model="modules" hint="lala" :value="$module['id']" name="modules" size="sm" :label="$module['name']"></x-form::lever> <x-form::lever wire:model="modules" hint="lala" :value="$module['id']" name="modules" size="sm" :label="$module['name']"></x-form::lever>
@endforeach @endforeach

View File

@ -0,0 +1,69 @@
<?php
namespace Modules\Nami\Components;
use App\Initialize\Actions\NamiLoginCheckAction;
use App\Setting\NamiSettings;
use Illuminate\Validation\ValidationException;
use Livewire\Component;
use Zoomyboy\LaravelNami\LoginException;
class SettingView extends Component
{
public $settingClass = NamiSettings::class;
public string $password = '';
public string $mglnr = '';
public string $default_group_id = '';
public function rules(): array
{
return [
'password' => 'required|string',
'default_group_id' => 'required',
'mglnr' => 'required',
];
}
public function mount(): void
{
$this->mglnr = app(NamiSettings::class)->mglnr;
$this->default_group_id = app(NamiSettings::class)->default_group_id;
}
public function save(): void
{
$validated = $this->validate();
try {
NamiLoginCheckAction::run([
'mglnr' => $this->mglnr,
'password' => $this->password,
]);
app(NamiSettings::class)->fill($validated)->save();
$this->dispatch('success', 'Einstellungen gespeichert.');
} catch (LoginException $e) {
throw ValidationException::withMessages([
'mglnr' => 'Login fehlgeschlagen.',
]);
}
}
public function render()
{
return <<<'HTML'
<x-page::setting-layout :active="$settingClass">
<x-slot:right>
<x-form::save-button form="namisettingform"></x-form::save-button>
</x-slot:right>
<form id="namisettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" wire:submit.prevent="save">
<x-ui::setting-intro class="col-span-full">
Hier kannst du deine Zugangsdaten zu NaMi anpassen, falls sich z.B. dein Passwort geändert hat.
</x-ui::setting-intro>
<x-form::text name="mglnr" wire:model="mglnr" label="Mitgliedsnummer"></x-form::text>
<x-form::text name="default_group_id" wire:model="default_group_id" label="Standard-Gruppierung"></x-form::text>
<x-form::text name="password" wire:model="password" label="Passwort" name="password" type="password"></x-form::text>
</form>
</x-page::setting-layout>
HTML;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Modules\Nami;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
use Modules\Nami\Components\SettingView;
class NamiServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
app(Router::class)->middleware(['web', 'auth:web'])->group(function ($router) {
$router->get('/setting/nami', SettingView::class)->name('setting.nami');
});
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace Modules\Module;
use App\Setting\NamiSettings;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Livewire\Livewire;
use Modules\Nami\Components\SettingView;
use Tests\TestCase;
use Zoomyboy\LaravelNami\Authentication\Auth;
use Zoomyboy\LaravelNami\Fakes\LoginFake;
uses(TestCase::class);
uses(DatabaseTransactions::class);
it('it renders page', function () {
test()->withoutExceptionHandling()->login()->loginNami();
test()->get(route('setting.nami'))->assertSeeLivewire(SettingView::class);
});
it('it displays active credentials', function () {
test()->withoutExceptionHandling()->login()->loginNami();
app(NamiSettings::class)->fill(['mglnr' => 903, 'password' => 'secret', 'default_group_id' => 55])->save();
Livewire::test(SettingView::class)
->assertSet('mglnr', 903)
->assertSet('password', '')
->assertSet('default_group_id', 55)
->assertSee('Mitgliedsnummer')
->assertSee('Standard-Gruppierung')
->assertSee('Passwort')
->assertDontSee('secret');
});
it('it saves credentials', function () {
test()->withoutExceptionHandling()->login()->loginNami();
Auth::success(100, 'secretneu');
app(NamiSettings::class)->fill(['mglnr' => 903, 'password' => 'secret', 'default_group_id' => 55])->save();
Livewire::test(SettingView::class)
->set('mglnr', 100)
->set('password', 'secretneu')
->set('default_group_id', 80)
->call('save')
->assertHasNoErrors()
->assertDispatched('success', 'Einstellungen gespeichert.');
$this->assertEquals(100, app(NamiSettings::class)->mglnr);
$this->assertEquals('secretneu', app(NamiSettings::class)->password);
$this->assertEquals('80', app(NamiSettings::class)->default_group_id);
});
it('validates fields', function ($name, $value, $errors) {
test()->withoutExceptionHandling()->login()->loginNami();
Auth::success(100, 'secretneu');
app(NamiSettings::class)->fill(['mglnr' => 903, 'password' => 'secret', 'default_group_id' => 55])->save();
Livewire::test(SettingView::class)
->set('mglnr', 100)
->set('password', 'secretneu')
->set('default_group_id', 80)
->set($name, $value)
->call('save')
->assertHasErrors($errors)
->assertNotDispatched('success');
})->with([
['password', '', ['password' => 'required']],
['mglnr', '', ['mglnr' => 'required']],
['default_group_id', '', ['default_group_id' => 'required']],
['default_group_id', null, ['default_group_id' => 'required']],
]);
it('it throws error when saving failed', function () {
test()->withoutExceptionHandling()->login()->loginNami();
app(NamiSettings::class)->fill(['mglnr' => 903, 'password' => 'secret', 'default_group_id' => 55])->save();
Livewire::test(SettingView::class)
->set('mglnr', 100)
->set('password', 'secretneu')
->set('default_group_id', 80)
->call('save')
->assertHasErrors(['mglnr' => 'Login fehlgeschlagen.'])
->assertNotDispatched('success');
$settings = app(NamiSettings::class)->refresh();
$this->assertEquals(903, $settings->mglnr);
$this->assertEquals('secret', $settings->password);
$this->assertEquals('55', $settings->default_group_id);
});

View File

@ -1,47 +0,0 @@
<template>
<page-layout>
<template #right>
<f-save-button form="namisettingform"></f-save-button>
</template>
<setting-layout>
<form id="namisettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" @submit.prevent="submit">
<div class="col-span-full text-gray-100 mb-3">
<p class="text-sm">Hier kannst du deine Zugangsdaten zu NaMi anpassen, falls sich z.B. dein Passwort geändert hat.</p>
</div>
<f-text id="mglnr" v-model="inner.mglnr" label="Mitgliedsnummer"></f-text>
<f-text id="default_group_id" v-model="inner.default_group_id" label="Standard-Gruppierung"></f-text>
<f-text id="password" v-model="inner.password" label="Passwort" name="password" type="password"></f-text>
</form>
</setting-layout>
</page-layout>
</template>
<script>
import SettingLayout from './Layout.vue';
export default {
components: {
SettingLayout,
},
props: {
data: {
type: Object,
default: () => {
return {};
},
},
},
data: function () {
return {
inner: {...this.data},
};
},
methods: {
submit() {
this.$inertia.post('/setting/nami', this.inner, {
onSuccess: () => this.$success('Einstellungen gespeichert.'),
});
},
},
};
</script>

View File

@ -4,7 +4,7 @@
<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" />
<title>{{ $title ?? 'Page Title' }}</title> <title>{{ session()->get('title') }} | {{ config('app.name') }}</title>
<meta name="socketport" content="{{env('SOCKET_PORT')}}" /> <meta name="socketport" content="{{env('SOCKET_PORT')}}" />
<meta name="adrema_base_url" content="/"> <meta name="adrema_base_url" content="/">
@if(auth()->id()) @if(auth()->id())

View File

@ -61,8 +61,6 @@ use App\Maildispatcher\Actions\EditAction;
use App\Maildispatcher\Actions\IndexAction; use App\Maildispatcher\Actions\IndexAction;
use App\Maildispatcher\Actions\StoreAction as MaildispatcherStoreAction; use App\Maildispatcher\Actions\StoreAction as MaildispatcherStoreAction;
use App\Maildispatcher\Actions\UpdateAction as MaildispatcherUpdateAction; use App\Maildispatcher\Actions\UpdateAction as MaildispatcherUpdateAction;
use App\Mailgateway\Actions\StoreAction;
use App\Mailgateway\Actions\UpdateAction;
use App\Member\Actions\ExportAction; use App\Member\Actions\ExportAction;
use App\Member\Actions\MemberDeleteAction; use App\Member\Actions\MemberDeleteAction;
use App\Member\Actions\MemberResyncAction; use App\Member\Actions\MemberResyncAction;
@ -112,8 +110,6 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::post('/contribution-validate', ContributionValidateAction::class)->name('contribution.validate'); Route::post('/contribution-validate', ContributionValidateAction::class)->name('contribution.validate');
// ----------------------------------- mail ------------------------------------ // ----------------------------------- mail ------------------------------------
Route::post('/api/mailgateway', StoreAction::class)->name('mailgateway.store');
Route::patch('/api/mailgateway/{mailgateway}', UpdateAction::class)->name('mailgateway.update');
Route::get('/maildispatcher', IndexAction::class)->name('maildispatcher.index'); Route::get('/maildispatcher', IndexAction::class)->name('maildispatcher.index');
Route::get('/maildispatcher/create', CreateAction::class)->name('maildispatcher.create'); Route::get('/maildispatcher/create', CreateAction::class)->name('maildispatcher.create');
Route::get('/maildispatcher/{maildispatcher}', EditAction::class)->name('maildispatcher.edit'); Route::get('/maildispatcher/{maildispatcher}', EditAction::class)->name('maildispatcher.edit');

View File

@ -2,14 +2,10 @@
arch() arch()
->expect('App') ->expect('App')
->not->toUse(['die', 'dd', 'dump']); ->not->toUse(['die', 'dd', 'dump'])
->not->toHaveFileSystemPermissions('0777');
arch() arch()
->expect(globArch('App\*\Models')) ->expect('Modules')
->toExtend('Illuminate\Database\Eloquent\Model') ->not->toUse(['die', 'dd', 'dump'])
->toBeClasses();
arch('app')
->expect('App')
->not->toHaveFileSystemPermissions('0777'); ->not->toHaveFileSystemPermissions('0777');

View File

@ -6,8 +6,8 @@ use \Mockery as M;
use App\Activity; use App\Activity;
use App\Maildispatcher\Models\Localmaildispatcher; use App\Maildispatcher\Models\Localmaildispatcher;
use App\Maildispatcher\Models\Maildispatcher; use App\Maildispatcher\Models\Maildispatcher;
use App\Mailgateway\Models\Mailgateway; use Modules\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Types\LocalType; use Modules\Mailgateway\Types\LocalType;
use App\Member\Member; use App\Member\Member;
use App\Member\Membership; use App\Member\Membership;
use Tests\EndToEndTestCase; use Tests\EndToEndTestCase;

View File

@ -4,11 +4,10 @@ namespace Tests\EndToEnd\Maildispatcher;
use App\Maildispatcher\Actions\ResyncAction; use App\Maildispatcher\Actions\ResyncAction;
use App\Maildispatcher\Models\Maildispatcher; use App\Maildispatcher\Models\Maildispatcher;
use App\Mailgateway\Models\Mailgateway; use Modules\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Types\LocalType; use Modules\Mailgateway\Types\LocalType;
use App\Member\FilterScope; use App\Member\FilterScope;
use App\Member\Member; use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\EndToEndTestCase; use Tests\EndToEndTestCase;
class UpdateTest extends EndToEndTestCase class UpdateTest extends EndToEndTestCase

View File

@ -4,6 +4,7 @@ namespace Tests\RequestFactories;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Worksome\RequestFactories\RequestFactory; use Worksome\RequestFactories\RequestFactory;
use Modules\Mailgateway\Types\MailmanType;
class MailmanTypeRequest extends RequestFactory class MailmanTypeRequest extends RequestFactory
{ {
@ -49,4 +50,9 @@ class MailmanTypeRequest extends RequestFactory
: Http::response([], 401); : Http::response([], 401);
}); });
} }
public function toData(): MailmanType
{
return MailmanType::from($this->create());
}
} }

View File

@ -174,10 +174,17 @@ class TestCase extends BaseTestCase
return $this; return $this;
}); });
Testable::macro('setArray', function ($attributes) { Testable::macro('setArray', function ($attributes, $value = null) {
$self = $this; $self = $this;
foreach ($attributes as $key => $value) { if ($value === null) {
$self = $this->set($key, $value); foreach ($attributes as $key => $value) {
$self = $this->set($key, $value);
}
return $self;
}
foreach ($value as $key => $v) {
$self = $self->set($attributes . '.' . $key, $v);
} }
return $self; return $self;