From 7a39798e8b6c6ab1624721f495a950873cc4a61a Mon Sep 17 00:00:00 2001 From: philipp lang <philipp@aweos.de> Date: Sun, 20 Oct 2024 21:19:07 +0200 Subject: [PATCH] Add mailgateway --- app/Setting/SettingServiceProvider.php | 2 - modules/Mailgateway/Components/Form.php | 119 ++++++++++++++++ .../Mailgateway/Components/SettingView.php | 21 +++ .../Components/setting-view.blade.php | 37 +++++ modules/Mailgateway/IndexTest.php | 39 ++++++ .../MailgatewayServiceProvider.php | 37 +++++ modules/Mailgateway/MailgatewaySettings.php | 26 ++++ modules/Mailgateway/StoreTest.php | 128 ++++++++++++++++++ modules/Mailgateway/UpdateTest.php | 104 ++++++++++++++ resources/js/components/ui/BooleanDisplay.vue | 32 ----- 10 files changed, 511 insertions(+), 34 deletions(-) create mode 100644 modules/Mailgateway/Components/Form.php create mode 100644 modules/Mailgateway/Components/SettingView.php create mode 100644 modules/Mailgateway/Components/setting-view.blade.php create mode 100644 modules/Mailgateway/IndexTest.php create mode 100644 modules/Mailgateway/MailgatewayServiceProvider.php create mode 100644 modules/Mailgateway/MailgatewaySettings.php create mode 100644 modules/Mailgateway/StoreTest.php create mode 100644 modules/Mailgateway/UpdateTest.php delete mode 100644 resources/js/components/ui/BooleanDisplay.vue diff --git a/app/Setting/SettingServiceProvider.php b/app/Setting/SettingServiceProvider.php index 4889a3f9..c452886b 100644 --- a/app/Setting/SettingServiceProvider.php +++ b/app/Setting/SettingServiceProvider.php @@ -4,7 +4,6 @@ namespace App\Setting; use App\Fileshare\FileshareSettings; use App\Form\FormSettings; -use App\Mailgateway\MailgatewaySettings; use Modules\Module\ModuleSettings; use App\Prevention\PreventionSettings; use App\Setting\Data\SettingSynthesizer; @@ -35,7 +34,6 @@ class SettingServiceProvider extends ServiceProvider { app(SettingFactory::class)->register(ModuleSettings::class); app(SettingFactory::class)->register(InvoiceSettings::class); - app(SettingFactory::class)->register(MailgatewaySettings::class); app(SettingFactory::class)->register(NamiSettings::class); app(SettingFactory::class)->register(FormSettings::class); app(SettingFactory::class)->register(FileshareSettings::class); diff --git a/modules/Mailgateway/Components/Form.php b/modules/Mailgateway/Components/Form.php new file mode 100644 index 00000000..9eb57979 --- /dev/null +++ b/modules/Mailgateway/Components/Form.php @@ -0,0 +1,119 @@ +<?php + +namespace Modules\Mailgateway\Components; + +use App\Mailgateway\Models\Mailgateway; +use Illuminate\Support\Collection; +use Illuminate\Validation\Rule; +use Illuminate\Validation\ValidationException; +use Livewire\Attributes\On; +use Livewire\Attributes\Validate; +use Livewire\Component; + +class Form extends Component +{ + + public string $id = ''; + public string $name = ''; + public string $domain = ''; + public array $params = []; + #[Validate('required')] + public ?string $cls = null; + public Collection $types; + + public function rules() + { + return [ + 'name' => 'required|string|max:255', + 'domain' => 'required|string|max:255', + 'cls' => ['required', 'string', 'max:255', Rule::in(app('mail-gateways'))], + 'params' => 'present|array', + ...$this->cls ? collect($this->cls::rules($this->id ? 'updateValidator' : 'storeValidator'))->mapWithKeys(fn ($rules, $key) => ["params.{$key}" => $rules]) : [], + ]; + } + + public function validationAttributes(): array + { + return [ + 'cls' => 'Typ', + 'name' => 'Beschreibung', + 'domain' => 'Domain', + ...$this->cls ? collect($this->cls::fieldNames())->mapWithKeys(fn ($attribute, $key) => ["params.{$key}" => $attribute]) : [], + ]; + } + + public function mount(?string $model = null): void + { + $this->types = app('mail-gateways')->map(fn ($gateway) => [ + 'name' => $gateway::name(), + 'id' => $gateway, + ]); + + $model = Mailgateway::find($model); + + if ($model) { + $this->id = $model->id; + $this->name = $model->name; + $this->domain = $model->domain; + $this->cls = get_class($model->type); + $this->params = (array) $model->type; + } + } + + public function updatedType(string $type): void + { + $this->params = $type::defaults(); + } + + public function fields(): array + { + return $this->cls ? $this->cls::fields() : []; + } + + #[On('onStoreFromModal')] + public function onSave(): void + { + $this->validate(); + + if (!app($this->cls)->setParams($this->params)->works()) { + throw ValidationException::withMessages(['connection' => 'Verbindung fehlgeschlagen.']); + } + + $payload = [ + 'name' => $this->name, + 'domain' => $this->domain, + 'type' => ['cls' => $this->cls, 'params' => $this->params], + ]; + if ($this->id) { + Mailgateway::find($this->id)->update($payload); + } else { + Mailgateway::create($payload); + } + $this->dispatch('closeModal'); + $this->dispatch('refresh'); + $this->dispatch('success', 'Erfolgreich gespeichert.'); + } + + public function render() + { + return <<<'HTML' + <div> + <form class="grid grid-cols-2 gap-3"> + <x-form::text name="name" wire:model="name" label="Beschreibung" 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 /> + @foreach($this->fields() as $index => $field) + <x-form::text + wire:key="index" + wire:model="params.{{$field['name']}}" + :label="$field['label']" + :type="$field['type']" + :name="$field['name']" + :required="str_contains('required', $field['storeValidator'])" + ></x-form::text> + @endforeach + </form> + </div> + HTML; + } +} diff --git a/modules/Mailgateway/Components/SettingView.php b/modules/Mailgateway/Components/SettingView.php new file mode 100644 index 00000000..2ec0c789 --- /dev/null +++ b/modules/Mailgateway/Components/SettingView.php @@ -0,0 +1,21 @@ +<?php + +namespace Modules\Mailgateway\Components; + +use App\Mailgateway\Models\Mailgateway; +use Livewire\Component; +use Modules\Mailgateway\MailgatewaySettings; + +class SettingView extends Component +{ + public string $settingClass = MailgatewaySettings::class; + + public $listeners = ['refresh' => '$refresh']; + + public function render() + { + return view('mailgateway::setting-view', [ + 'data' => Mailgateway::get(), + ]); + } +} diff --git a/modules/Mailgateway/Components/setting-view.blade.php b/modules/Mailgateway/Components/setting-view.blade.php new file mode 100644 index 00000000..d4b2c45f --- /dev/null +++ b/modules/Mailgateway/Components/setting-view.blade.php @@ -0,0 +1,37 @@ +<x-page::setting-layout :active="$settingClass"> + <div> + <x-ui::table> + <thead> + <th>Bezeichnung</th> + <th>Domain</th> + <th>Typ</th> + <th>Prüfung</th> + <th>Aktion</th> + </thead> + + <x-ui::action wire:click.prevent="$dispatch('openModal', {component: 'modules.mailgateway.components.form', props: {model: ''}, title: 'Verbindung erstellen'})" icon="plus" variant="danger">Neu</x-ui::action> + + @foreach ($data as $index => $gateway) + <tr wire:key="$index"> + <td>{{ $gateway->name }}</td> + <td>{{ $gateway->domain }}</td> + <td>{{ $gateway->type::name() }}</td> + <td> + <x-ui::boolean-display :value="$gateway->type->works()" + hint="Verbindungsstatus" + right="Verbindung erfolgreich" + wrong="Verbindung fehlgeschlagen" + ></x-ui::boolean-display> + </td> + <td> + <x-ui::action wire:click="$dispatch('openModal', { + component: 'modules.mailgateway.components.form', + props: {model: '{{$gateway->id}}'}, + title: 'Verbindung {{$gateway->name}} bearbeiten'} + )" icon="pencil" variant="warning">Bearbeiten</x-ui::action> + </td> + </tr> + @endforeach + </x-ui::table> + </div> +</x-page::setting-layout> diff --git a/modules/Mailgateway/IndexTest.php b/modules/Mailgateway/IndexTest.php new file mode 100644 index 00000000..31419d47 --- /dev/null +++ b/modules/Mailgateway/IndexTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Tests\Feature\Mailgateway; + +use App\Mailgateway\Models\Mailgateway; +use App\Mailgateway\Types\LocalType; +use App\Mailgateway\Types\MailmanType; +use Illuminate\Foundation\Testing\DatabaseTransactions; +use Livewire\Livewire; +use Modules\Mailgateway\Components\SettingView; +use Tests\RequestFactories\MailmanTypeRequest; +use Tests\TestCase; + +uses(DatabaseTransactions::class); +uses(TestCase::class); + +it('test it can view index page', function () { + test()->login()->loginNami(); + test()->get('/setting/mailgateway')->assertSeeLivewire(SettingView::class); +}); + +it('test it displays local gateways', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + Mailgateway::factory()->type(LocalType::class, [])->name('Lore')->domain('example.com')->create(); + + Livewire::test(SettingView::class) + ->assertSeeHtml('example.com') + ->assertSeeHtml('Lore') + ->assertSeeHtml('Lokal') + ->assertSeeHtml('Verbindung erfolgreich'); +}); + +it('displays mailman gateways', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + $typeParams = MailmanTypeRequest::new()->succeeds()->create(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']); + Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(); + + Livewire::test(SettingView::class)->assertSeeHtml('Verbindung erfolgreich'); +}); diff --git a/modules/Mailgateway/MailgatewayServiceProvider.php b/modules/Mailgateway/MailgatewayServiceProvider.php new file mode 100644 index 00000000..f0f2b64a --- /dev/null +++ b/modules/Mailgateway/MailgatewayServiceProvider.php @@ -0,0 +1,37 @@ +<?php + +namespace Modules\Mailgateway; + +use App\Setting\SettingFactory; +use Illuminate\Routing\Router; +use Illuminate\Support\Facades\View; +use Illuminate\Support\ServiceProvider; +use Modules\Mailgateway\Components\SettingView; + +class MailgatewayServiceProvider extends ServiceProvider +{ + /** + * Register services. + * + * @return void + */ + public function register() + { + } + + /** + * Bootstrap services. + * + * @return void + */ + public function boot() + { + app(SettingFactory::class)->register(MailgatewaySettings::class); + + app(Router::class)->middleware(['web', 'auth:web'])->group(function ($router) { + $router->get('/setting/mailgateway', SettingView::class)->name('setting.mailgateway'); + }); + + View::addNamespace('mailgateway', __DIR__ . '/Components'); + } +} diff --git a/modules/Mailgateway/MailgatewaySettings.php b/modules/Mailgateway/MailgatewaySettings.php new file mode 100644 index 00000000..419fa797 --- /dev/null +++ b/modules/Mailgateway/MailgatewaySettings.php @@ -0,0 +1,26 @@ +<?php + +namespace Modules\Mailgateway; + +use App\Setting\LocalSettings; + +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 []; + } +} diff --git a/modules/Mailgateway/StoreTest.php b/modules/Mailgateway/StoreTest.php new file mode 100644 index 00000000..b2c2f7dd --- /dev/null +++ b/modules/Mailgateway/StoreTest.php @@ -0,0 +1,128 @@ +<?php + +namespace Tests\Feature\Mailgateway; + +use App\Mailgateway\Types\LocalType; +use App\Mailgateway\Types\MailmanType; +use Illuminate\Foundation\Testing\DatabaseTransactions; +use Livewire\Livewire; +use Modules\Mailgateway\Components\Form; +use Tests\RequestFactories\MailmanTypeRequest; +use Tests\TestCase; + +uses(DatabaseTransactions::class); +uses(TestCase::class); + +it('test it saves a mail gateway', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + + Livewire::test(Form::class) + ->set('name', 'lala') + ->set('domain', 'example.com') + ->set('cls', LocalType::class) + ->call('onSave') + ->assertDispatched('closeModal') + ->assertDispatched('refresh') + ->assertDispatched('success'); + + $this->assertDatabaseHas('mailgateways', [ + 'domain' => 'example.com', + 'name' => 'lala', + 'type' => json_encode([ + 'cls' => LocalType::class, + 'params' => [], + ]), + ]); +}); + +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) { + test()->withoutExceptionHandling()->login()->loginNami(); + + Livewire::test(Form::class) + ->set('name', 'lala') + ->set('domain', 'example.com') + ->set('cls', LocalType::class) + ->setArray($attributes) + ->call('onSave') + ->assertHasErrors($errors) + ->assertNotDispatched('closeModal') + ->assertNotDispatched('refresh') + ->assertNotDispatched('success'); +})->with([ + [['name' => ''], ['name' => 'required']], + [['domain' => ''], ['domain' => 'required']], +]); + +it('test it validates mailman type', function (array $attributes, array $errors) { + test()->withoutExceptionHandling()->login()->loginNami(); + + Livewire::test(Form::class) + ->set('name', 'lala') + ->set('domain', 'example.com') + ->set('cls', MailmanType::class) + ->set('params.url', 'exampl.com') + ->set('params.user', '::user::') + ->set('params.password', 'password') + ->setArray($attributes) + ->call('onSave') + ->assertHasErrors($errors) + ->assertNotDispatched('closeModal'); +})->with([ + [['params.url' => ''], ['params.url' => 'required']], + [['params.user' => ''], ['params.user' => 'required']], + [['params.password' => ''], ['params.password' => 'required']], + [['params.owner' => ''], ['params.owner' => 'required']], + [['params.owner' => 'aaa'], ['params.owner' => 'email']], +]); + +it('test it stores mailman gateway', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + + $typeParams = MailmanTypeRequest::new()->succeeds()->create(['url' => 'https://example.com', 'user' => 'user', 'password' => 'secret', 'owner' => 'owner@example.com']); + + Livewire::test(Form::class) + ->setArray([ + 'name' => 'lala', + 'domain' => 'https://example.com', + 'cls' => MailmanType::class, + 'params' => $typeParams + ]) + ->call('onSave') + ->assertDispatched('closeModal'); + + $this->assertDatabaseHas('mailgateways', [ + 'type' => json_encode([ + 'cls' => MailmanType::class, + 'params' => $typeParams, + ]), + 'name' => 'lala', + 'domain' => 'https://example.com', + ]); +}); + +it('test it checks mailman connection', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + + $typeParams = MailmanTypeRequest::new()->fails()->create(['url' => 'https://example.com', 'user' => 'user', 'password' => 'secret', 'owner' => 'owner@example.com']); + + Livewire::test(Form::class) + ->setArray([ + 'name' => 'lala', + 'domain' => 'https://example.com', + 'cls' => MailmanType::class, + 'params' => $typeParams + ]) + ->call('onSave') + ->assertHasErrors('connection') + ->assertNotDispatched('closeModal'); + + $this->assertDatabaseCount('mailgateways', 0); +}); diff --git a/modules/Mailgateway/UpdateTest.php b/modules/Mailgateway/UpdateTest.php new file mode 100644 index 00000000..790af3fb --- /dev/null +++ b/modules/Mailgateway/UpdateTest.php @@ -0,0 +1,104 @@ +<?php + +namespace Tests\Feature\Mailgateway; + +use App\Mailgateway\Models\Mailgateway; +use App\Mailgateway\Types\LocalType; +use App\Mailgateway\Types\MailmanType; +use Illuminate\Foundation\Testing\DatabaseTransactions; +use Livewire\Livewire; +use Modules\Mailgateway\Components\Form; +use Phake; +use Tests\RequestFactories\MailmanTypeRequest; +use Tests\TestCase; + +uses(DatabaseTransactions::class); +uses(TestCase::class); + +it('test it sets attributes for mailman', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + + $typeParams = MailmanTypeRequest::new()->create(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']); + $mailgateway = Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(['name' => '::name::', 'domain' => 'example.com']); + + Livewire::test(Form::class, ['model' => $mailgateway->id]) + ->assertSet('id', $mailgateway->id) + ->assertSet('name', '::name::') + ->assertSet('domain', 'example.com') + ->assertSet('cls', MailmanType::class) + ->assertSet('params.url', 'https://mailman.example.com') + ->assertSet('params.user', 'user') + ->assertSet('params.password', 'password') + ->assertSet('params.owner', 'owner'); +}); + +it('test it sets attributes for local', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + + $mailgateway = Mailgateway::factory()->type(LocalType::class, [])->create(['name' => '::name::', 'domain' => 'example.com']); + + Livewire::test(Form::class, ['model' => $mailgateway->id]) + ->assertSet('name', '::name::') + ->assertSet('domain', 'example.com') + ->assertSet('cls', LocalType::class) + ->assertSet('params', []); +}); + +it('test it validates type', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + + $mailgateway = Mailgateway::factory()->type(LocalType::class, [])->create(['name' => '::name::', 'domain' => 'example.com']); + + Livewire::test(Form::class, ['model' => $mailgateway->id]) + ->set('cls', '') + ->assertHasErrors(['cls' => 'required']); +}); + +it('test it updates a mailman gateway without updating password', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + + $typeParams = MailmanTypeRequest::new()->succeeds()->create(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner@example.com']); + $mailgateway = Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(['name' => '::name::', 'domain' => 'example.com']); + + Livewire::test(Form::class, ['model' => $mailgateway->id]) + ->set('name', '::newname::') + ->call('onSave') + ->assertHasNoErrors(); + + $this->assertDatabaseCount('mailgateways', 1); + $this->assertDatabaseHas('mailgateways', [ + 'name' => '::newname::', + 'type' => json_encode(['cls' => MailmanType::class, 'params' => $typeParams]), + ]); +}); + +it('test it updates a mailman gateway with password', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + + $typeParams = MailmanTypeRequest::new()->create(['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']); + $mailgateway = Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(); + + Livewire::test(Form::class, ['model' => $mailgateway->id]) + ->set('params.user', 'newuser') + ->call('onSave') + ->assertHasNoErrors(); + + $this->assertDatabaseCount('mailgateways', 1); + $this->assertDatabaseHas('mailgateways', [ + 'type' => json_encode(['cls' => MailmanType::class, 'params' => $newTypeParams]), + ]); +}); + +it('test it checks mailgateway connection when updating', function () { + test()->withoutExceptionHandling()->login()->loginNami(); + + $typeParams = MailmanTypeRequest::new()->create(['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']); + $mailgateway = Mailgateway::factory()->type(MailmanType::class, $typeParams)->create(); + + Livewire::test(Form::class, ['model' => $mailgateway->id]) + ->set('params.user', 'newuser') + ->call('onSave') + ->assertHasErrors('connection'); +}); diff --git a/resources/js/components/ui/BooleanDisplay.vue b/resources/js/components/ui/BooleanDisplay.vue deleted file mode 100644 index 4536b3ed..00000000 --- a/resources/js/components/ui/BooleanDisplay.vue +++ /dev/null @@ -1,32 +0,0 @@ -<template> - <div v-tooltip="longLabel" class="flex space-x-2 items-center"> - <div class="border-2 rounded-full w-5 h-5 flex items-center justify-center" :class="value ? (dark ? 'border-green-500' : 'border-green-700') : dark ? 'border-red-500' : 'border-red-700'"> - <ui-sprite :src="value ? 'check' : 'close'" :class="value ? (dark ? 'text-green-600' : 'text-green-800') : dark ? 'text-red-600' : 'text-red-800'" class="w-3 h-3 flex-none"></ui-sprite> - </div> - <div class="text-gray-400 text-xs" v-text="label"></div> - </div> -</template> - -<script> -export default { - props: { - value: { - required: true, - type: Boolean, - }, - label: { - type: String, - default: () => '', - }, - longLabel: { - default: function () { - return null; - }, - }, - dark: { - type: Boolean, - default: () => false, - }, - }, -}; -</script>