Add index for mail gateways

This commit is contained in:
Philipp Lang 2023-06-01 15:02:35 +02:00
parent d2a000cb31
commit 27c61ff8af
17 changed files with 464 additions and 65 deletions

View File

@ -0,0 +1,33 @@
<?php
namespace App\Mailgateway\Actions;
use App\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Resources\MailgatewayResource;
use Illuminate\Database\Eloquent\Builder;
use Inertia\Inertia;
use Inertia\Response;
use Lorisleiva\Actions\Concerns\AsAction;
class IndexAction
{
use AsAction;
/**
* @return Builder<Mailgateway>
*/
public function handle(): Builder
{
return (new Mailgateway())->newQuery();
}
public function asController(): Response
{
session()->put('menu', 'setting');
session()->put('title', 'E-Mail-Verbindungen');
return Inertia::render('mailgateway/Index', [
'data' => MailgatewayResource::collection($this->handle()->paginate(10)),
]);
}
}

View File

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

View File

@ -0,0 +1,30 @@
<?php
namespace App\Mailgateway;
use App\Mailgateway\Actions\IndexAction;
use App\Setting\Contracts\Indexable;
use App\Setting\LocalSettings;
class MailgatewaySettings extends LocalSettings implements Indexable
{
public static function group(): string
{
return 'mailgateway';
}
public static function slug(): string
{
return 'mailgateway';
}
public static function indexAction(): string
{
return IndexAction::class;
}
public static function title(): string
{
return 'E-Mail-Verbindungen';
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Mailgateway\Models;
use App\Mailgateway\Casts\TypeCast;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Mailgateway extends Model
{
use HasFactory;
use HasUuids;
public $casts = ['type' => TypeCast::class];
public $guarded = [];
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Mailgateway\Resources;
use App\Lib\HasMeta;
use App\Mailgateway\Models\Mailgateway;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin Mailgateway
*/
class MailgatewayResource extends JsonResource
{
use HasMeta;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
*
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'name' => $this->name,
'domain' => $this->domain,
'type_human' => $this->type::name(),
'works' => $this->type->works(),
];
}
public static function meta(): array
{
return [
'links' => [
'store' => route('api.mailgateway.store'),
],
];
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Mailgateway\Types;
class LocalType extends Type
{
public static function name(): string
{
return 'Lokal';
}
public function works(): bool
{
return true;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Mailgateway\Types;
abstract class Type
{
abstract public static function name(): string;
abstract public function works(): bool;
}

View File

@ -2,6 +2,7 @@
namespace App\Providers;
use App\Mailgateway\Types\LocalType;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;
@ -26,6 +27,10 @@ class AppServiceProvider extends ServiceProvider
return $this;
});
app()->bind('mail-gateways', fn () => collect([
LocalType::class,
]));
}
/**

View File

@ -3,6 +3,7 @@
namespace App\Setting;
use App\Invoice\InvoiceSettings;
use App\Mailgateway\MailgatewaySettings;
use App\Mailman\MailmanSettings;
use Illuminate\Support\ServiceProvider;
@ -27,5 +28,6 @@ class SettingServiceProvider extends ServiceProvider
{
app(SettingFactory::class)->register(InvoiceSettings::class);
app(SettingFactory::class)->register(MailmanSettings::class);
app(SettingFactory::class)->register(MailgatewaySettings::class);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Database\Factories\Mailgateway\Models;
use App\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Types\Type;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Mailgateway\Models\Mailgateway>
*/
class MailgatewayFactory extends Factory
{
protected $model = Mailgateway::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'name' => $this->faker->words(5, true),
'type' => [
'class' => app('mail-gateways')->random(),
'params' => [],
],
'domain' => $this->faker->safeEmailDomain(),
];
}
/**
* @param class-string<Type> $type
* @param array<string, mixed> $params
*/
public function type(string $type, array $params): self
{
return $this->state(['type' => [
'class' => $type,
'params' => $params,
]]);
}
public function name(string $name): self
{
return $this->state(['name' => $name]);
}
public function domain(string $domain): self
{
return $this->state(['domain' => $domain]);
}
}

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('mailgateways', function (Blueprint $table) {
$table->uuid('id');
$table->string('name');
$table->string('domain');
$table->json('type');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('mailgateways');
}
};

View File

@ -0,0 +1,74 @@
<template>
<page-layout>
<template #toolbar>
<page-toolbar-button @click.prevent="popup = true" color="primary" icon="plus">Neue Verbindung</page-toolbar-button>
</template>
<ui-popup heading="Neue Verbindung" v-if="popup === true" @close="popup = false">
<div>
<div class="grid grid-cols-2 gap-3 mt-6">
<a href="#" @click.prevent="submit" class="text-center btn btn-danger">Speichern</a>
<a
href="#"
@click.prevent="
value = {};
popup = false;
"
class="text-center btn btn-primary"
>Abbrechen</a
>
</div>
</div>
</ui-popup>
<setting-layout>
<div class="w-full h-full pb-6">
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm hidden md:table">
<thead>
<th>Name</th>
<th>Domain</th>
<th>Typ</th>
<th>Prüfung</th>
<th>Aktion</th>
</thead>
<tr v-for="(gateway, index) in inner" :key="index">
<td v-text="gateway.name"></td>
<td v-text="gateway.domain"></td>
<td v-text="gateway.type_human"></td>
<td>
<ui-boolean-display
:value="gateway.works"
long-label="Verbindungsstatus"
:label="gateway.works ? 'Verbindung erfolgreich' : 'Verbindung fehlgeschlagen'"
></ui-boolean-display>
</td>
<td></td>
</tr>
</table>
<div class="px-6">
<ui-pagination class="mt-4" :value="data.meta" :only="['data']"></ui-pagination>
</div>
</div>
</setting-layout>
</page-layout>
</template>
<script>
import SettingLayout from '../setting/Layout.vue';
export default {
data: function () {
return {
popup: false,
inner: [...this.data.data],
meta: {...this.data.meta},
};
},
props: {
data: {},
},
components: {
SettingLayout,
},
};
</script>

View File

@ -1,31 +1,32 @@
<template>
<form id="billsettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" @submit.prevent="submit">
<f-save-button form="billsettingform"></f-save-button>
<f-text label="Absender" hint="Absender-Name in Kurzform, i.d.R. der kurze Stammesname" name="from" id="from" v-model="inner.from"></f-text>
<f-text label="Absender (lang)" v-model="inner.from_long" name="from_long" id="from_long" hint="Absender-Name in Langform, i.d.R. der Stammesname"></f-text>
<h2 class="text-lg font-semibold text-gray-300 col-span-2 mt-5">Kontaktdaten</h2>
<div class="col-span-2 text-gray-300 text-sm">Diese Kontaktdaten stehen im Absender-Bereich auf der Rechnung.</div>
<f-text label="Straße" v-model="inner.address" name="address" id="address"></f-text>
<f-text label="PLZ" v-model="inner.zip" name="zip" id="zip"></f-text>
<f-text label="Ort" v-model="inner.place" name="place" id="place"></f-text>
<f-text label="E-Mail-Adresse" v-model="inner.email" name="email" id="email"></f-text>
<f-text label="Telefonnummer" v-model="inner.mobile" name="mobile" id="mobile"></f-text>
<f-text label="Webseite" v-model="inner.website" name="website" id="website"></f-text>
<f-text label="IBAN" v-model="inner.iban" name="iban" id="iban"></f-text>
<f-text label="BIC" v-model="inner.bic" name="bic" id="bic"></f-text>
</form>
<page-layout>
<setting-layout>
<form id="billsettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" @submit.prevent="submit">
<f-save-button form="billsettingform"></f-save-button>
<f-text label="Absender" hint="Absender-Name in Kurzform, i.d.R. der kurze Stammesname" name="from" id="from" v-model="inner.from"></f-text>
<f-text label="Absender (lang)" v-model="inner.from_long" name="from_long" id="from_long" hint="Absender-Name in Langform, i.d.R. der Stammesname"></f-text>
<h2 class="text-lg font-semibold text-gray-300 col-span-2 mt-5">Kontaktdaten</h2>
<div class="col-span-2 text-gray-300 text-sm">Diese Kontaktdaten stehen im Absender-Bereich auf der Rechnung.</div>
<f-text label="Straße" v-model="inner.address" name="address" id="address"></f-text>
<f-text label="PLZ" v-model="inner.zip" name="zip" id="zip"></f-text>
<f-text label="Ort" v-model="inner.place" name="place" id="place"></f-text>
<f-text label="E-Mail-Adresse" v-model="inner.email" name="email" id="email"></f-text>
<f-text label="Telefonnummer" v-model="inner.mobile" name="mobile" id="mobile"></f-text>
<f-text label="Webseite" v-model="inner.website" name="website" id="website"></f-text>
<f-text label="IBAN" v-model="inner.iban" name="iban" id="iban"></f-text>
<f-text label="BIC" v-model="inner.bic" name="bic" id="bic"></f-text>
</form>
</setting-layout>
</page-layout>
</template>
<script>
import AppLayout from '../../layouts/AppLayout.vue';
import SettingLayout from './Layout.vue';
export default {
layout: [AppLayout, SettingLayout],
data: function () {
return {
inner: {},
inner: {...this.data},
};
},
props: {
@ -36,8 +37,8 @@ export default {
this.$inertia.post('/setting/bill', this.inner);
},
},
created() {
this.inner = this.data;
components: {
SettingLayout,
},
};
</script>

View File

@ -1,17 +1,15 @@
<template>
<page-layout>
<div class="flex grow relative">
<ui-tabs v-model="active" :entries="$page.props.setting_menu"></ui-tabs>
<slot></slot>
</div>
</page-layout>
<div class="flex grow relative">
<ui-tabs v-model="active" :entries="$page.props.setting_menu"></ui-tabs>
<slot></slot>
</div>
</template>
<script>
export default {
data: function () {
return {
innerActive: 0,
innerActive: this.$page.props.setting_menu.findIndex((menu) => menu.is_active),
};
},
computed: {
@ -19,19 +17,15 @@ export default {
get() {
return this.innerActive;
},
set(v, old) {
set(v) {
var _self = this;
this.$inertia.visit(this.$page.props.setting_menu[v].url, {
onSuccess(page) {
console.log('A');
onSuccess() {
_self.innerActive = v;
},
});
},
},
},
mounted() {
this.innerActive = this.$page.props.setting_menu.findIndex((menu) => menu.is_active);
},
};
</script>

View File

@ -1,41 +1,42 @@
<template>
<form id="mailmansettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" @submit.prevent="submit">
<f-save-button form="mailmansettingform"></f-save-button>
<div class="col-span-full text-gray-100 mb-3">
<p class="text-sm">
Scoutrobot kann automatisch Mailinglisten erstellen, wenn es mit einem existierenden
<a href="https://docs.mailman3.org/en/latest/">Mailman Server</a> verbunden wird. Mailman ist ein OpenSource-Mailinglisten-System, um E-Mails an mehrere Leute zu senden.
</p>
<p class="text-sm mt-1">Scoutrobot wird nach der Ersteinrichtung deine Mitglieder zu bestehenden E-Mail-Verteilern hinzufügen.</p>
</div>
<div>
<f-switch id="is_active" v-model="inner.is_active" label="Mailman-Synchronisation aktiv"></f-switch>
</div>
<div class="flex h-full items-center">
<svg-sprite :src="stateDisplay.icon" :class="stateDisplay.text" class="w-5 h-5"></svg-sprite>
<span class="ml-3" :class="stateDisplay.text" v-text="stateDisplay.label"></span>
</div>
<f-text label="URL" hint="URL der Mailman Api" name="base_url" id="base_url" v-model="inner.base_url"></f-text>
<f-text label="Benutzername" name="username" id="username" v-model="inner.username"></f-text>
<f-text type="password" label="Passwort" name="password" id="password" v-model="inner.password"></f-text>
<f-select label="Liste für alle Mitglieder" name="all_list" id="all_list" v-model="inner.all_list" :options="lists"></f-select>
<f-select label="Liste für Eltern" name="all_parents_list" id="all_parents_list" v-model="inner.all_parents_list" :options="lists"></f-select>
<f-select label="Liste für aktive Leiter" name="active_leaders_list" id="active_leaders_list" v-model="inner.active_leaders_list" :options="lists"></f-select>
<f-select label="Liste für passive Leiter" name="passive_leaders_list" id="passive_leaders_list" v-model="inner.passive_leaders_list" :options="lists"></f-select>
<div></div>
</form>
<page-layout>
<setting-layout>
<form id="mailmansettingform" class="grow p-6 grid grid-cols-2 gap-3 items-start content-start" @submit.prevent="submit">
<f-save-button form="mailmansettingform"></f-save-button>
<div class="col-span-full text-gray-100 mb-3">
<p class="text-sm">
Scoutrobot kann automatisch Mailinglisten erstellen, wenn es mit einem existierenden
<a href="https://docs.mailman3.org/en/latest/">Mailman Server</a> verbunden wird. Mailman ist ein OpenSource-Mailinglisten-System, um E-Mails an mehrere Leute zu senden.
</p>
<p class="text-sm mt-1">Scoutrobot wird nach der Ersteinrichtung deine Mitglieder zu bestehenden E-Mail-Verteilern hinzufügen.</p>
</div>
<div>
<f-switch id="is_active" v-model="inner.is_active" label="Mailman-Synchronisation aktiv"></f-switch>
</div>
<div class="flex h-full items-center">
<svg-sprite :src="stateDisplay.icon" :class="stateDisplay.text" class="w-5 h-5"></svg-sprite>
<span class="ml-3" :class="stateDisplay.text" v-text="stateDisplay.label"></span>
</div>
<f-text label="URL" hint="URL der Mailman Api" name="base_url" id="base_url" v-model="inner.base_url"></f-text>
<f-text label="Benutzername" name="username" id="username" v-model="inner.username"></f-text>
<f-text type="password" label="Passwort" name="password" id="password" v-model="inner.password"></f-text>
<f-select label="Liste für alle Mitglieder" name="all_list" id="all_list" v-model="inner.all_list" :options="lists"></f-select>
<f-select label="Liste für Eltern" name="all_parents_list" id="all_parents_list" v-model="inner.all_parents_list" :options="lists"></f-select>
<f-select label="Liste für aktive Leiter" name="active_leaders_list" id="active_leaders_list" v-model="inner.active_leaders_list" :options="lists"></f-select>
<f-select label="Liste für passive Leiter" name="passive_leaders_list" id="passive_leaders_list" v-model="inner.passive_leaders_list" :options="lists"></f-select>
<div></div>
</form>
</setting-layout>
</page-layout>
</template>
<script>
import AppLayout from '../../layouts/AppLayout.vue';
import SettingLayout from './Layout.vue';
export default {
layout: [AppLayout, SettingLayout],
data: function () {
return {
inner: {},
inner: {...this.data},
};
},
props: {
@ -78,8 +79,8 @@ export default {
});
},
},
created() {
this.inner = this.data;
components: {
SettingLayout,
},
};
</script>

View File

@ -78,4 +78,7 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::get('/contribution', ContributionFormAction::class)->name('contribution.form');
Route::get('/contribution-generate', ContributionGenerateAction::class)->name('contribution.generate');
Route::post('/contribution-validate', ContributionValidateAction::class)->name('contribution.validate');
// -------------------------------- Mailgateway --------------------------------
Route::post('/api/mailgateway', fn () => '')->name('api.mailgateway.store');
});

View File

@ -0,0 +1,49 @@
<?php
namespace Tests\Feature\Mailgateway;
use App\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Types\LocalType;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class IndexTest extends TestCase
{
use DatabaseTransactions;
public function setUp(): void
{
parent::setUp();
$this->login()->loginNami();
}
public function testItCanViewIndexPage(): void
{
$response = $this->get('/setting/mailgateway');
$response->assertOk();
}
public function testItDisplaysGateways(): void
{
$this->withoutExceptionHandling();
Mailgateway::factory()->type(LocalType::class, [])->name('Lore')->domain('example.com')->create();
$response = $this->get('/setting/mailgateway');
$this->assertInertiaHas('example.com', $response, 'data.data.0.domain');
$this->assertInertiaHas('Lore', $response, 'data.data.0.name');
$this->assertInertiaHas('Lokal', $response, 'data.data.0.type_human');
$this->assertInertiaHas(true, $response, 'data.data.0.works');
}
public function testItHasMeta(): void
{
$this->withoutExceptionHandling();
$response = $this->get('/setting/mailgateway');
$this->assertInertiaHas(route('api.mailgateway.store'), $response, 'data.meta.links.store');
}
}