Compare commits
31 Commits
f21c636803
...
af848e51c6
Author | SHA1 | Date |
---|---|---|
philipp lang | af848e51c6 | |
philipp lang | 49f972bc1b | |
philipp lang | b26ac83031 | |
philipp lang | 21433624c5 | |
philipp lang | 98c2972575 | |
philipp lang | d590fbd325 | |
philipp lang | 9535a50c4a | |
philipp lang | d46dccf4e6 | |
philipp lang | 3009003545 | |
philipp lang | ff47b024f5 | |
philipp lang | 8077f36724 | |
philipp lang | 908f99f403 | |
philipp lang | ee5b923f25 | |
philipp lang | ce7f86b7c0 | |
philipp lang | 89803b80a1 | |
philipp lang | d45d8f561f | |
philipp lang | c94facf794 | |
philipp lang | cef1c28df1 | |
philipp lang | ff846d0929 | |
philipp lang | ba59783415 | |
philipp lang | c0031cd4f0 | |
philipp lang | 56dd5cf146 | |
philipp lang | d096a1026b | |
philipp lang | fd8bf40090 | |
philipp lang | 62e122eef1 | |
philipp lang | baa4b4a32c | |
philipp lang | be8d1f5ab7 | |
philipp lang | 3ec27b6707 | |
philipp lang | 4ccc4097d6 | |
philipp lang | bf973d4139 | |
philipp lang | ef0b0198fe |
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,6 +6,9 @@ use App\Mailgateway\Models\Mailgateway;
|
||||||
use App\Mailgateway\Resources\MailgatewayResource;
|
use App\Mailgateway\Resources\MailgatewayResource;
|
||||||
use App\Setting\LocalSettings;
|
use App\Setting\LocalSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
class MailgatewaySettings extends LocalSettings
|
class MailgatewaySettings extends LocalSettings
|
||||||
{
|
{
|
||||||
public static function group(): string
|
public static function group(): string
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
namespace App\Mailgateway\Models;
|
namespace App\Mailgateway\Models;
|
||||||
|
|
||||||
use App\Mailgateway\Casts\TypeCast;
|
use App\Mailgateway\Types\Type as TypesType;
|
||||||
use Database\Factories\Mailgateway\Models\MailgatewayFactory;
|
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;
|
||||||
|
use Nette\Utils\Type;
|
||||||
|
|
||||||
class Mailgateway extends Model
|
class Mailgateway extends Model
|
||||||
{
|
{
|
||||||
|
@ -14,6 +15,6 @@ class Mailgateway extends Model
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use HasUuids;
|
use HasUuids;
|
||||||
|
|
||||||
public $casts = ['type' => TypeCast::class];
|
public $casts = ['type' => TypesType::class];
|
||||||
public $guarded = [];
|
public $guarded = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @mixin Mailgateway
|
* @mixin Mailgateway
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
class MailgatewayResource extends JsonResource
|
class MailgatewayResource extends JsonResource
|
||||||
{
|
{
|
||||||
|
@ -27,7 +28,6 @@ class MailgatewayResource extends JsonResource
|
||||||
'domain' => $this->domain,
|
'domain' => $this->domain,
|
||||||
'type_human' => $this->type::name(),
|
'type_human' => $this->type::name(),
|
||||||
'works' => $this->type->works(),
|
'works' => $this->type->works(),
|
||||||
'type' => $this->type->toResource(),
|
|
||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
'links' => [
|
'links' => [
|
||||||
'update' => route('mailgateway.update', ['mailgateway' => $this->getModel()]),
|
'update' => route('mailgateway.update', ['mailgateway' => $this->getModel()]),
|
||||||
|
|
|
@ -45,32 +45,28 @@ class MailmanType extends Type
|
||||||
'name' => 'url',
|
'name' => 'url',
|
||||||
'label' => 'URL',
|
'label' => 'URL',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'storeValidator' => 'required|max:255',
|
'validator' => 'required|max:255',
|
||||||
'updateValidator' => 'required|max:255',
|
|
||||||
'default' => '',
|
'default' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'user',
|
'name' => 'user',
|
||||||
'label' => 'Benutzer',
|
'label' => 'Benutzer',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'storeValidator' => 'required|max:255',
|
'validator' => 'required|max:255',
|
||||||
'updateValidator' => 'required|max:255',
|
|
||||||
'default' => '',
|
'default' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'password',
|
'name' => 'password',
|
||||||
'label' => 'Passwort',
|
'label' => 'Passwort',
|
||||||
'type' => 'password',
|
'type' => 'password',
|
||||||
'storeValidator' => 'required|max:255',
|
'validator' => 'required|max:255',
|
||||||
'updateValidator' => 'nullable|max:255',
|
|
||||||
'default' => '',
|
'default' => '',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'owner',
|
'name' => 'owner',
|
||||||
'label' => 'E-Mail-Adresse des Eigentümers',
|
'label' => 'E-Mail-Adresse des Eigentümers',
|
||||||
'type' => 'email',
|
'type' => 'email',
|
||||||
'storeValidator' => 'required|email|max:255',
|
'validator' => 'required|email|max:255',
|
||||||
'updateValidator' => 'required|email|max:255',
|
|
||||||
'default' => '',
|
'default' => '',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -4,9 +4,15 @@ namespace App\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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,24 +66,13 @@ abstract class Type
|
||||||
/**
|
/**
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public static function rules(string $validator): array
|
public static function rules(): array
|
||||||
{
|
{
|
||||||
return collect(static::fields())->mapWithKeys(fn ($field) => [
|
return collect(static::fields())->mapWithKeys(fn ($field) => [
|
||||||
$field['name'] => $field[$validator],
|
$field['name'] => $field['validator'],
|
||||||
])->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
|
||||||
*/
|
*/
|
||||||
|
@ -93,8 +88,8 @@ abstract class Type
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->list($name, $domain)
|
$this->list($name, $domain)
|
||||||
->filter(fn ($listEntry) => $results->doesntContain(fn ($r) => $r->is($listEntry)))
|
->filter(fn ($listEntry) => $results->doesntContain(fn ($r) => $r->is($listEntry)))
|
||||||
->each(fn ($listEntry) => $this->remove($name, $domain, $listEntry->email));
|
->each(fn ($listEntry) => $this->remove($name, $domain, $listEntry->email));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -85,7 +85,7 @@ class MailmanService
|
||||||
return app(Paginator::class)->result(
|
return app(Paginator::class)->result(
|
||||||
fn ($page) => $this->http()->get("/lists/{$list->listId}/roster/member?page={$page}&count=10"),
|
fn ($page) => $this->http()->get("/lists/{$list->listId}/roster/member?page={$page}&count=10"),
|
||||||
function ($response) use ($list) {
|
function ($response) use ($list) {
|
||||||
throw_unless($response->ok(), MailmanServiceException::class, 'Fetching members for listId '.$list->listId.' failed.');
|
throw_unless($response->ok(), MailmanServiceException::class, 'Fetching members for listId ' . $list->listId . ' failed.');
|
||||||
/** @var array<int, array{email: string, self_link: string}>|null */
|
/** @var array<int, array{email: string, self_link: string}>|null */
|
||||||
$entries = data_get($response->json(), 'entries', []);
|
$entries = data_get($response->json(), 'entries', []);
|
||||||
throw_if(is_null($entries), MailmanServiceException::class, 'Failed getting member list from response');
|
throw_if(is_null($entries), MailmanServiceException::class, 'Failed getting member list from response');
|
||||||
|
@ -112,7 +112,7 @@ class MailmanService
|
||||||
'pre_confirmed' => 'true',
|
'pre_confirmed' => 'true',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
throw_unless(201 === $response->status(), MailmanServiceException::class, 'Adding member '.$email.' to '.$list->listId.' failed');
|
throw_unless(201 === $response->status(), MailmanServiceException::class, 'Adding member ' . $email . ' to ' . $list->listId . ' failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function removeMember(Member $member): void
|
public function removeMember(Member $member): void
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace App\Setting;
|
||||||
|
|
||||||
use App\Fileshare\FileshareSettings;
|
use App\Fileshare\FileshareSettings;
|
||||||
use App\Form\FormSettings;
|
use App\Form\FormSettings;
|
||||||
use App\Mailgateway\MailgatewaySettings;
|
|
||||||
use Modules\Module\ModuleSettings;
|
use Modules\Module\ModuleSettings;
|
||||||
use App\Prevention\PreventionSettings;
|
use App\Prevention\PreventionSettings;
|
||||||
use App\Setting\Data\SettingSynthesizer;
|
use App\Setting\Data\SettingSynthesizer;
|
||||||
|
@ -35,7 +34,6 @@ class SettingServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
app(SettingFactory::class)->register(ModuleSettings::class);
|
app(SettingFactory::class)->register(ModuleSettings::class);
|
||||||
app(SettingFactory::class)->register(InvoiceSettings::class);
|
app(SettingFactory::class)->register(InvoiceSettings::class);
|
||||||
app(SettingFactory::class)->register(MailgatewaySettings::class);
|
|
||||||
app(SettingFactory::class)->register(NamiSettings::class);
|
app(SettingFactory::class)->register(NamiSettings::class);
|
||||||
app(SettingFactory::class)->register(FormSettings::class);
|
app(SettingFactory::class)->register(FormSettings::class);
|
||||||
app(SettingFactory::class)->register(FileshareSettings::class);
|
app(SettingFactory::class)->register(FileshareSettings::class);
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Enums;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
enum Variant: string
|
||||||
|
{
|
||||||
|
|
||||||
|
case PRIMARY = 'primary';
|
||||||
|
case SECONDARY = 'secondary';
|
||||||
|
case PRIMARYLIGHT = 'primary-light';
|
||||||
|
case WARNING = 'warning';
|
||||||
|
case INFO = 'info';
|
||||||
|
case DANGER = 'danger';
|
||||||
|
|
||||||
|
public function foreground(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::PRIMARY => 'text-primary-300',
|
||||||
|
self::SECONDARY => 'text-primary-400',
|
||||||
|
self::PRIMARYLIGHT => 'text-primary-200',
|
||||||
|
self::WARNING => 'text-yellow-300',
|
||||||
|
self::INFO => 'text-blue-300',
|
||||||
|
self::DANGER => 'text-red-100',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function background(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::PRIMARY => 'bg-primary-700',
|
||||||
|
self::SECONDARY => 'bg-primary-800',
|
||||||
|
self::PRIMARYLIGHT => 'bg-primary-600',
|
||||||
|
self::WARNING => 'bg-yellow-700',
|
||||||
|
self::INFO => 'bg-blue-700',
|
||||||
|
self::DANGER => 'bg-red-400',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hoverForeground(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::PRIMARY => 'hover:text-primary-100',
|
||||||
|
self::SECONDARY => 'hover:text-primary-200',
|
||||||
|
self::PRIMARYLIGHT => 'hover:text-primary-100',
|
||||||
|
self::WARNING => 'hover:text-yellow-100',
|
||||||
|
self::INFO => 'hover:text-blue-100',
|
||||||
|
self::DANGER => 'hover:text-red-100',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hoverBackground(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::PRIMARY => 'hover:bg-primary-500',
|
||||||
|
self::SECONDARY => 'hover:bg-primary-600',
|
||||||
|
self::PRIMARYLIGHT => 'hover:bg-primary-500',
|
||||||
|
self::WARNING => 'hover:bg-yellow-500',
|
||||||
|
self::INFO => 'hover:bg-blue-500',
|
||||||
|
self::DANGER => 'hover:bg-red-500',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromString(string $input): self
|
||||||
|
{
|
||||||
|
return collect(static::cases())
|
||||||
|
->first(fn ($variant) => $variant->value === $input)
|
||||||
|
?: throw new InvalidArgumentException("Unknown variant: {$input} - Available Variants: " . self::values()->implode(', '));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function values(): Collection
|
||||||
|
{
|
||||||
|
return collect(static::cases())->map(fn ($variant) => $variant->value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ class Hint extends Component
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return <<<'HTML'
|
return <<<'HTML'
|
||||||
<div class="h-full items-center flex absolute top-0 right-0">
|
<div {{ $attributes->merge(['class' => 'h-full items-center flex absolute top-0 right-0']) }}>
|
||||||
<div x-tooltip.raw="{{$slot}}" class="mr-2">
|
<div x-tooltip.raw="{{$slot}}" class="mr-2">
|
||||||
<x-ui::sprite src="info-button" class="w-5 h-5 text-primary-700"></x-ui::sprite>
|
<x-ui::sprite src="info-button" class="w-5 h-5 text-primary-700"></x-ui::sprite>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,7 +21,7 @@ class Label extends Component
|
||||||
<span class="font-semibold leading-none text-gray-400 group-[.size-default]:text-sm group-[.size-sm]:text-xs">
|
<span class="font-semibold leading-none text-gray-400 group-[.size-default]:text-sm group-[.size-sm]:text-xs">
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
@if ($required)
|
@if ($required)
|
||||||
<span x-if="required" class="text-red-800"> *</span>
|
<span class="text-red-800"> *</span>
|
||||||
@endif
|
@endif
|
||||||
</span>
|
</span>
|
||||||
HTML;
|
HTML;
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Form;
|
||||||
|
|
||||||
|
use App\View\Traits\HasFormDimensions;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class Select extends Component
|
||||||
|
{
|
||||||
|
|
||||||
|
use HasFormDimensions;
|
||||||
|
|
||||||
|
public string $id;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public string $name,
|
||||||
|
public string $size = 'default',
|
||||||
|
public ?string $hint = null,
|
||||||
|
public bool $required = false,
|
||||||
|
public string $label = '',
|
||||||
|
public $options = [],
|
||||||
|
public bool $disabled = false,
|
||||||
|
) {
|
||||||
|
$this->id = str()->uuid()->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<label class="flex flex-col group {{$heightClass}}" for="{{$id}}" style="{{$heightVars}}">
|
||||||
|
@if ($label)
|
||||||
|
<x-form::label :required="$required">{{$label}}</x-form::label>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="relative flex-none flex">
|
||||||
|
<select {{$attributes}} @if($disabled) disabled @endif name="{{$name}}"
|
||||||
|
class="
|
||||||
|
w-full h-[var(--height)] border-gray-600 border-solid text-gray-300 bg-gray-700 leading-none rounded-lg
|
||||||
|
group-[.size-default]:border-2 group-[.size-sm]:border
|
||||||
|
group-[.size-default]:text-sm group-[.size-sm]:text-xs
|
||||||
|
group-[.size-default]:px-2 group-[.size-sm]:px-1
|
||||||
|
py-0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<option value="">-- kein --</option>
|
||||||
|
@foreach ($options as $option)
|
||||||
|
<option value="{{$option['id']}}">{{ $option['name'] }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@if($hint)
|
||||||
|
<x-form::hint class="right-6">{{$hint}}</x-form::hint>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</label>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ class Text extends Component
|
||||||
<input
|
<input
|
||||||
id="{{$id}}"
|
id="{{$id}}"
|
||||||
type="{{$type}}"
|
type="{{$type}}"
|
||||||
|
@if ($type === 'password') autocomplete="off" @endif
|
||||||
placeholder=""
|
placeholder=""
|
||||||
class="
|
class="
|
||||||
w-full h-[var(--height)] border-gray-600 border-solid text-gray-300 bg-gray-700 leading-none rounded-lg
|
w-full h-[var(--height)] border-gray-600 border-solid text-gray-300 bg-gray-700 leading-none rounded-lg
|
||||||
|
|
|
@ -24,7 +24,7 @@ class Layout extends Component
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return <<<'HTML'
|
return <<<'HTML'
|
||||||
<div class="grow flex flex-col">
|
<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="{{ session()->get('title') }}">
|
||||||
<x-slot:beforeTitle>
|
<x-slot:beforeTitle>
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Page;
|
||||||
|
|
||||||
|
use Livewire\Attributes\On;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Modal extends Component
|
||||||
|
{
|
||||||
|
|
||||||
|
public ?string $component = null;
|
||||||
|
public array $props = [];
|
||||||
|
public string $key = '';
|
||||||
|
public array $actions = [];
|
||||||
|
public ?string $size = null;
|
||||||
|
public string $title = '';
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On('openModal')]
|
||||||
|
public function onOpenModal(string $component, string $title, array $props = [], array $actions = ['storeable', 'closeable'], string $size = "xl"): void
|
||||||
|
{
|
||||||
|
$this->component = $component;
|
||||||
|
$this->props = $props;
|
||||||
|
$this->size = $size;
|
||||||
|
$this->title = $title;
|
||||||
|
$this->key = md5(json_encode(['component' => $component, 'props' => $props]));
|
||||||
|
$this->actions = $this->parseActions($actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseActions(array $actions): array
|
||||||
|
{
|
||||||
|
return collect($actions)->map(function ($action) {
|
||||||
|
if ($action === 'closeable') {
|
||||||
|
return ['event' => 'closeModal', 'icon' => 'close', 'label' => 'Schließen'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'storeable') {
|
||||||
|
return ['event' => 'onStoreFromModal', 'icon' => 'save', 'label' => 'Speichern'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $action;
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On('closeModal')]
|
||||||
|
public function onCloseModal(): void
|
||||||
|
{
|
||||||
|
$this->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sizeClass(): string
|
||||||
|
{
|
||||||
|
if ($this->size === 'lg') {
|
||||||
|
return 'max-w-lg';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->size === 'xl') {
|
||||||
|
return 'max-w-xl';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<div>
|
||||||
|
@if($component)
|
||||||
|
<div class="fixed z-40 top-0 left-0 w-full h-full flex items-center justify-center p-6 bg-black/60 backdrop-blur-sm" @click.self="$dispatch('closeModal')">
|
||||||
|
<div
|
||||||
|
class="relative rounded-lg p-8 bg-zinc-800 shadow-2xl shadow-black border border-zinc-700 border-solid w-full max-h-full flex flex-col overflow-auto {{$this->sizeClass()}}"
|
||||||
|
>
|
||||||
|
<div class="flex">
|
||||||
|
<h3 class="font-semibold text-primary-200 text-xl grow">{{$title}}</h3>
|
||||||
|
<div class="flex space-x-6">
|
||||||
|
@foreach ($this->actions as $action)
|
||||||
|
<a x-tooltip.raw="{{$action['label']}}" href="#" @click.prevent="$dispatch('{{$action['event']}}')">
|
||||||
|
<x-ui::sprite :src="$action['icon']" class="text-zinc-400 w-6 h-6"></x-ui::sprite>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-primary-100 group is-popup grow flex flex-col mt-3">
|
||||||
|
<div>
|
||||||
|
@if ($component)
|
||||||
|
@livewire($component, $props, key($key))
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div></div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,11 +25,13 @@ class SettingLayout extends Component
|
||||||
return <<<'HTML'
|
return <<<'HTML'
|
||||||
<x-page::layout>
|
<x-page::layout>
|
||||||
<x-slot:right>
|
<x-slot:right>
|
||||||
{{ $right }}
|
{{ $right ?? '' }}
|
||||||
</x-slot:right>
|
</x-slot:right>
|
||||||
<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>
|
||||||
{{ $slot }}
|
<div class="grow">
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</x-page::layout>
|
</x-page::layout>
|
||||||
HTML;
|
HTML;
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Traits;
|
||||||
|
|
||||||
|
use App\View\Enums\Variant;
|
||||||
|
|
||||||
|
trait HasColors
|
||||||
|
{
|
||||||
|
public function bgColor(string $variant): string
|
||||||
|
{
|
||||||
|
$variant = Variant::fromString($variant);
|
||||||
|
|
||||||
|
return implode(' ', [
|
||||||
|
$variant->background(),
|
||||||
|
$variant->hoverBackground(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fgColor(string $variant): string
|
||||||
|
{
|
||||||
|
$variant = Variant::fromString($variant);
|
||||||
|
|
||||||
|
return implode(' ', [
|
||||||
|
$variant->foreground(),
|
||||||
|
$variant->hoverForeground(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function allColors(string $variant): string
|
||||||
|
{
|
||||||
|
return "{$this->bgColor($variant)} {$this->fgColor($variant)}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Ui;
|
||||||
|
|
||||||
|
use App\View\Traits\HasColors;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class Action extends Component
|
||||||
|
{
|
||||||
|
|
||||||
|
use HasColors;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public string $icon,
|
||||||
|
public string $variant = 'primary'
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<a x-tooltip.raw="{{$slot}}" href="#" {{ $attributes }} class="inline-flex
|
||||||
|
w-6 h-5 flex items-center justify-center rounded {{ $allColors($variant) }}
|
||||||
|
">
|
||||||
|
<x-ui::sprite class="w-3 h-3 flex-none" :src="$icon"></x-ui::sprite>
|
||||||
|
</a>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Ui;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class BooleanDisplay extends Component
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public bool $value,
|
||||||
|
public string $hint,
|
||||||
|
public string $right,
|
||||||
|
public string $wrong,
|
||||||
|
public bool $dark = false,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function spriteClass(): string
|
||||||
|
{
|
||||||
|
return $this->value ? 'text-green-800 group-[.dark]:text-green-600' : 'text-red-800 group-[.dark]:text-red-600';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<div x-tooltip.raw="{{$hint}}" class="flex space-x-2 items-center group @if($dark) dark @endif">
|
||||||
|
<div class="border-2 rounded-full w-5 h-5 flex items-center justify-center
|
||||||
|
@if ($value) border-green-700 group-[.dark]:border-green-500
|
||||||
|
@else border-red-700 group-[.dark]:border-red-500
|
||||||
|
@endif
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<x-ui::sprite :src="$value ? 'check ' :'close'" class="w-3 h-3 flex-none {{$spriteClass}}"></x-ui::sprite>
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-400 text-xs">{{ $value ? $right : $wrong }}</div>
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Ui;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class Table extends Component
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(public string $mode = 'dark')
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<div class="@container/table">
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0" class="w-full @if ($mode === 'dark') table-dark @else table-light @endif
|
||||||
|
[&_th]:text-left [&_th]:px-2 [&_th]:@4xl/table:px-6 [&_th]:text-gray-200 [&_th]:font-semibold [&_th]:py-3 [&_th]:border-gray-600 [&_th]:border-b
|
||||||
|
[&_tbody_tr]:text-gray-200 [&_tbody_tr]:duration-300 [&_tbody_tr]:rounded [&_tbody_tr:hover]:bg-gray-800
|
||||||
|
[&_tr_td]:py-1 [&_tr_td]:px-2 [&_tr_td]:@4xl/table:px-6
|
||||||
|
[&.table-light_th]:border-gray-500 [&.table-light_tbody_tr:hover]:bg-gray-700
|
||||||
|
|
||||||
|
|
||||||
|
">
|
||||||
|
{{ $slot }}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
|
@ -181,6 +181,7 @@ return [
|
||||||
Modules\Dashboard\DashboardServiceProvider::class,
|
Modules\Dashboard\DashboardServiceProvider::class,
|
||||||
Modules\Module\ModuleServiceProvider::class,
|
Modules\Module\ModuleServiceProvider::class,
|
||||||
Modules\Invoice\InvoiceServiceProvider::class,
|
Modules\Invoice\InvoiceServiceProvider::class,
|
||||||
|
Modules\Mailgateway\MailgatewayServiceProvider::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Database\Factories\Mailgateway\Models;
|
namespace Database\Factories\Mailgateway\Models;
|
||||||
|
|
||||||
use App\Mailgateway\Models\Mailgateway;
|
use App\Mailgateway\Models\Mailgateway;
|
||||||
|
use App\Mailgateway\Types\LocalType;
|
||||||
use App\Mailgateway\Types\Type;
|
use App\Mailgateway\Types\Type;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
@ -22,24 +23,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
|
||||||
|
|
|
@ -25,7 +25,14 @@ it('renders successfully', function () {
|
||||||
it('renders page successfully', function () {
|
it('renders page successfully', function () {
|
||||||
$this->login()->loginNami();
|
$this->login()->loginNami();
|
||||||
|
|
||||||
$this->get('/')->assertOk()->assertSee('Dashboard');
|
$this->get('/')->assertSeeLivewire(DashboardComponent::class)->assertSee('Dashboard');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows member component', function () {
|
||||||
|
$this->login()->loginNami();
|
||||||
|
|
||||||
|
Livewire::test(DashboardComponent::class)
|
||||||
|
->assertSee('Gruppierungs-Verteilung');
|
||||||
});
|
});
|
||||||
|
|
||||||
class ExampleBlock extends Block
|
class ExampleBlock extends Block
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Mailgateway\Components;
|
||||||
|
|
||||||
|
use App\Mailgateway\Models\Mailgateway;
|
||||||
|
use App\Mailgateway\Types\Type;
|
||||||
|
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 $name = '';
|
||||||
|
public string $domain = '';
|
||||||
|
public ?Type $type = null;
|
||||||
|
public Collection $types;
|
||||||
|
public ?Mailgateway $model = null;
|
||||||
|
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'domain' => 'required|string|max:255',
|
||||||
|
...$this->type ? collect($this->type::rules())->mapWithKeys(fn ($rules, $key) => ["params.{$key}" => $rules]) : [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validationAttributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'Typ',
|
||||||
|
'name' => 'Beschreibung',
|
||||||
|
'domain' => 'Domain',
|
||||||
|
...$this->type ? collect($this->type::fieldNames())->mapWithKeys(fn ($attribute, $key) => ["params.{$key}" => $attribute]) : [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount(?string $id = null): void
|
||||||
|
{
|
||||||
|
$this->types = app('mail-gateways')->map(fn ($gateway) => [
|
||||||
|
'name' => $gateway::name(),
|
||||||
|
'id' => $gateway,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
$this->model = Mailgateway::find($id);
|
||||||
|
$this->name = $this->model->name;
|
||||||
|
$this->domain = $this->model->domain;
|
||||||
|
$this->type = $this->model->type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fields(): array
|
||||||
|
{
|
||||||
|
return $this->type ? $this->type::fields() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[On('onStoreFromModal')]
|
||||||
|
public function onSave(): void
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
if (!$this->type->works()) {
|
||||||
|
throw ValidationException::withMessages(['connection' => 'Verbindung fehlgeschlagen.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'name' => $this->name,
|
||||||
|
'domain' => $this->domain,
|
||||||
|
'type' => $this->type,
|
||||||
|
];
|
||||||
|
if ($this->model) {
|
||||||
|
$this->model->update($payload);
|
||||||
|
} else {
|
||||||
|
Mailgateway::create($payload);
|
||||||
|
}
|
||||||
|
$this->dispatch('closeModal');
|
||||||
|
$this->dispatch('refresh-page');
|
||||||
|
$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="type" wire:model.live="type" label="Typ" :options="$types" required />
|
||||||
|
@foreach($this->fields() as $index => $field)
|
||||||
|
<x-form::text
|
||||||
|
wire:key="index"
|
||||||
|
wire:model="$type->{$field['name']}"
|
||||||
|
:label="$field['label']"
|
||||||
|
:type="$field['type']"
|
||||||
|
:name="$field['name']"
|
||||||
|
:required="str_contains('required', $field['validator'])"
|
||||||
|
></x-form::text>
|
||||||
|
@endforeach
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?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 function render()
|
||||||
|
{
|
||||||
|
return view('mailgateway::setting-view', [
|
||||||
|
'data' => Mailgateway::get(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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: {}, 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: {id: '{{$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>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Mailgateway;
|
||||||
|
|
||||||
|
use App\Mailgateway\Models\Mailgateway;
|
||||||
|
use App\Mailgateway\Types\LocalType;
|
||||||
|
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::from([]))->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()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']);
|
||||||
|
Mailgateway::factory()->type($typeParams->toData())->create();
|
||||||
|
|
||||||
|
Livewire::test(SettingView::class)->assertSeeHtml('Verbindung erfolgreich');
|
||||||
|
});
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
<?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('type', new LocalType())
|
||||||
|
->call('onSave')
|
||||||
|
->assertHasNoErrors()
|
||||||
|
->assertDispatched('closeModal')
|
||||||
|
->assertDispatched('refresh-page')
|
||||||
|
->assertDispatched('success');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('mailgateways', [
|
||||||
|
'domain' => 'example.com',
|
||||||
|
'name' => 'lala',
|
||||||
|
'type' => json_encode([
|
||||||
|
'type' => LocalType::class,
|
||||||
|
'data' => [],
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
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('type', new LocalType())
|
||||||
|
->setArray($attributes)
|
||||||
|
->call('onSave')
|
||||||
|
->assertHasErrors($errors)
|
||||||
|
->assertNotDispatched('closeModal')
|
||||||
|
->assertNotDispatched('refresh-page')
|
||||||
|
->assertNotDispatched('success');
|
||||||
|
})->with([
|
||||||
|
[['name' => ''], ['name' => 'required']],
|
||||||
|
[['domain' => ''], ['domain' => 'required']],
|
||||||
|
[['type' => ''], ['type' => '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);
|
||||||
|
});
|
|
@ -0,0 +1,108 @@
|
||||||
|
<?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 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()->state(['url' => 'https://mailman.example.com', 'user' => 'user', 'password' => 'password', 'owner' => 'owner']);
|
||||||
|
$mailgateway = Mailgateway::factory()->type($typeParams->toData())->create(['name' => '::name::', 'domain' => 'example.com']);
|
||||||
|
|
||||||
|
Livewire::test(Form::class, ['id' => $mailgateway->id])
|
||||||
|
->assertSet('model', fn ($m) => $m->is($mailgateway))
|
||||||
|
->assertSet('name', '::name::')
|
||||||
|
->assertSet('domain', 'example.com')
|
||||||
|
->assertSet(
|
||||||
|
'type',
|
||||||
|
fn ($type) => $type instanceof MailmanType
|
||||||
|
&& $type->url === 'https://mailman.example.com'
|
||||||
|
&& $type->user === 'user' &&
|
||||||
|
$type->password === 'password'
|
||||||
|
&& $type->owner === 'owner'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test it sets attributes for local', function () {
|
||||||
|
test()->withoutExceptionHandling()->login()->loginNami();
|
||||||
|
|
||||||
|
$mailgateway = Mailgateway::factory()->create(['name' => '::name::', 'domain' => 'example.com']);
|
||||||
|
|
||||||
|
Livewire::test(Form::class, ['id' => $mailgateway->id])
|
||||||
|
->assertSet('name', '::name::')
|
||||||
|
->assertSet('domain', 'example.com')
|
||||||
|
->assertSet('type', fn ($type) => $type instanceof LocalType);
|
||||||
|
});
|
||||||
|
|
||||||
|
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, ['id' => $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, ['id' => $mailgateway->id])
|
||||||
|
->set('name', '::newname::')
|
||||||
|
->call('onSave')
|
||||||
|
->assertHasNoErrors()
|
||||||
|
->assertDispatched('closeModal')
|
||||||
|
->assertDispatched('refresh-page')
|
||||||
|
->assertDispatched('success');
|
||||||
|
|
||||||
|
$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, ['id' => $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, ['id' => $mailgateway->id])
|
||||||
|
->set('params.user', 'newuser')
|
||||||
|
->call('onSave')
|
||||||
|
->assertHasErrors('connection');
|
||||||
|
});
|
|
@ -11,6 +11,7 @@
|
||||||
"@editorjs/paragraph": "^2.11.3",
|
"@editorjs/paragraph": "^2.11.3",
|
||||||
"@inertiajs/vue3": "^1.0.14",
|
"@inertiajs/vue3": "^1.0.14",
|
||||||
"@ryangjchandler/alpine-tooltip": "^2.0.1",
|
"@ryangjchandler/alpine-tooltip": "^2.0.1",
|
||||||
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@vitejs/plugin-vue": "^4.6.2",
|
"@vitejs/plugin-vue": "^4.6.2",
|
||||||
|
@ -998,6 +999,14 @@
|
||||||
"tippy.js": "^6.3.1"
|
"tippy.js": "^6.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/container-queries": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/forms": {
|
"node_modules/@tailwindcss/forms": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"@editorjs/paragraph": "^2.11.3",
|
"@editorjs/paragraph": "^2.11.3",
|
||||||
"@inertiajs/vue3": "^1.0.14",
|
"@inertiajs/vue3": "^1.0.14",
|
||||||
"@ryangjchandler/alpine-tooltip": "^2.0.1",
|
"@ryangjchandler/alpine-tooltip": "^2.0.1",
|
||||||
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@vitejs/plugin-vue": "^4.6.2",
|
"@vitejs/plugin-vue": "^4.6.2",
|
||||||
|
|
|
@ -5,6 +5,5 @@
|
||||||
@import 'base.css';
|
@import 'base.css';
|
||||||
@import 'layout';
|
@import 'layout';
|
||||||
@import 'buttons';
|
@import 'buttons';
|
||||||
@import 'table';
|
|
||||||
@import 'bool';
|
@import 'bool';
|
||||||
@import 'editor';
|
@import 'editor';
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
.custom-table {
|
|
||||||
width: 100%;
|
|
||||||
& > thead > th {
|
|
||||||
@apply text-left px-6 text-gray-200 font-semibold py-3 border-gray-600 border-b;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > tr {
|
|
||||||
@apply text-gray-200 transition-all duration-300 rounded hover:bg-gray-800;
|
|
||||||
& > td {
|
|
||||||
@apply py-1 px-6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.custom-table-sm {
|
|
||||||
& > thead > th {
|
|
||||||
@apply px-3 py-2;
|
|
||||||
}
|
|
||||||
& > tr {
|
|
||||||
& > td {
|
|
||||||
@apply py-1 px-3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.custom-table-light {
|
|
||||||
& > thead > th {
|
|
||||||
@apply border-gray-500;
|
|
||||||
}
|
|
||||||
& > td {
|
|
||||||
&:hover {
|
|
||||||
@apply bg-gray-700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.custom-table > * {
|
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
.custom-table > * > * {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -1,45 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="fixed z-40 top-0 left-0 w-full h-full flex items-center justify-center p-6 bg-black/60 backdrop-blur-sm">
|
|
||||||
<div
|
|
||||||
class="relative rounded-lg p-8 bg-zinc-800 shadow-2xl shadow-black border border-zinc-700 border-solid w-full max-h-full flex flex-col overflow-auto"
|
|
||||||
:class="full ? 'h-full' : innerWidth"
|
|
||||||
>
|
|
||||||
<div class="absolute top-0 right-0 mt-6 mr-6 flex space-x-6">
|
|
||||||
<slot name="actions"></slot>
|
|
||||||
<a href="#" @click.prevent="$emit('close')">
|
|
||||||
<ui-sprite src="close" class="text-zinc-400 w-6 h-6"></ui-sprite>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<h3 v-if="heading" class="font-semibold text-primary-200 text-xl" v-html="heading"></h3>
|
|
||||||
<div class="text-primary-100 group is-popup grow flex flex-col">
|
|
||||||
<suspense>
|
|
||||||
<div>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
<template #fallback>
|
|
||||||
<ui-loading></ui-loading>
|
|
||||||
</template>
|
|
||||||
</suspense>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
heading: {
|
|
||||||
type: String,
|
|
||||||
default: () => '',
|
|
||||||
},
|
|
||||||
innerWidth: {
|
|
||||||
default: () => 'max-w-xl',
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
full: {
|
|
||||||
type: Boolean,
|
|
||||||
default: () => false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -15,6 +15,7 @@
|
||||||
<body class="min-h-full flex flex-col">
|
<body class="min-h-full flex flex-col">
|
||||||
<livewire:page.sidebar />
|
<livewire:page.sidebar />
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
|
<livewire:page.modal />
|
||||||
<page-search-modal v-if="searchVisible" @close="searchVisible = false"></page-search-modal>
|
<page-search-modal v-if="searchVisible" @close="searchVisible = false"></page-search-modal>
|
||||||
@livewireScriptConfig
|
@livewireScriptConfig
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -78,6 +78,7 @@ use App\Membership\Actions\MembershipStoreAction;
|
||||||
use App\Membership\Actions\MembershipUpdateAction;
|
use App\Membership\Actions\MembershipUpdateAction;
|
||||||
use App\Payment\SubscriptionController;
|
use App\Payment\SubscriptionController;
|
||||||
|
|
||||||
|
Route::get('/lala', fn () => auth()->login(\App\User::first()));
|
||||||
Route::group(['namespace' => 'App\\Http\\Controllers'], function (): void {
|
Route::group(['namespace' => 'App\\Http\\Controllers'], function (): void {
|
||||||
Auth::routes(['register' => false]);
|
Auth::routes(['register' => false]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,5 +29,5 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
|
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms'), require('@tailwindcss/container-queries')],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature;
|
|
||||||
|
|
||||||
use App\Dashboard\Blocks\Block;
|
|
||||||
use App\Dashboard\DashboardFactory;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class DashboardTest extends TestCase
|
|
||||||
{
|
|
||||||
use DatabaseTransactions;
|
|
||||||
|
|
||||||
public function testItDisplaysBlock(): void
|
|
||||||
{
|
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
app(DashboardFactory::class)->purge();
|
|
||||||
app(DashboardFactory::class)->register(ExampleBlock::class);
|
|
||||||
|
|
||||||
$this->login()->loginNami();
|
|
||||||
|
|
||||||
$response = $this->get('/');
|
|
||||||
|
|
||||||
$this->assertInertiaHas(['class' => 'name'], $response, 'blocks.0.data');
|
|
||||||
$this->assertInertiaHas('Example', $response, 'blocks.0.title');
|
|
||||||
$this->assertInertiaHas('exa', $response, 'blocks.0.component');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItDisplaysUserAvatar(): void
|
|
||||||
{
|
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
|
|
||||||
$this->login()->loginNami();
|
|
||||||
auth()->user()->update(['firstname' => 'Bob', 'lastname' => 'Dylan', 'email' => 'max@email.com']);
|
|
||||||
|
|
||||||
$this->get('/')
|
|
||||||
->assertInertiaPath('auth.user.firstname', 'Bob')
|
|
||||||
->assertInertiaPath('auth.user.avatar_url', 'https://www.gravatar.com/avatar/' . hash('sha256', 'max@email.com'))
|
|
||||||
->assertInertiaPath('auth.user.lastname', 'Dylan');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExampleBlock extends Block
|
|
||||||
{
|
|
||||||
public function title(): string
|
|
||||||
{
|
|
||||||
return 'Example';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
public function data(): array
|
|
||||||
{
|
|
||||||
return ['class' => 'name'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function component(): string
|
|
||||||
{
|
|
||||||
return 'exa';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Mailgateway;
|
|
||||||
|
|
||||||
use App\Mailgateway\Models\Mailgateway;
|
|
||||||
use App\Mailgateway\Types\LocalType;
|
|
||||||
use App\Mailgateway\Types\MailmanType;
|
|
||||||
use App\Mailman\Support\MailmanService;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Phake;
|
|
||||||
use Tests\RequestFactories\MailmanTypeRequest;
|
|
||||||
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 testItDisplaysLocalGateways(): void
|
|
||||||
{
|
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
$mailgateway = 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');
|
|
||||||
$this->assertInertiaHas($mailgateway->id, $response, 'data.data.0.id');
|
|
||||||
$this->assertInertiaHas(route('mailgateway.update', ['mailgateway' => $mailgateway->id]), $response, 'data.data.0.links.update');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItDisplaysMailmanGateways(): void
|
|
||||||
{
|
|
||||||
$this->stubIo(MailmanService::class, function ($mock) {
|
|
||||||
Phake::when($mock)->setCredentials()->thenReturn($mock);
|
|
||||||
Phake::when($mock)->check()->thenReturn(true);
|
|
||||||
});
|
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
Mailgateway::factory()->type(MailmanType::class, MailmanTypeRequest::new()->create())->create();
|
|
||||||
|
|
||||||
$this->get('/setting/mailgateway')->assertOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItHasMeta(): void
|
|
||||||
{
|
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
|
|
||||||
$response = $this->get('/setting/mailgateway');
|
|
||||||
|
|
||||||
$this->assertInertiaHas(route('mailgateway.store'), $response, 'data.meta.links.store');
|
|
||||||
$this->assertInertiaHas([
|
|
||||||
'id' => null,
|
|
||||||
'name' => '-- kein --',
|
|
||||||
], $response, 'data.meta.types.0');
|
|
||||||
$this->assertInertiaHas([
|
|
||||||
'id' => LocalType::class,
|
|
||||||
'name' => 'Lokal',
|
|
||||||
], $response, 'data.meta.types.1');
|
|
||||||
$this->assertInertiaHas([
|
|
||||||
'id' => MailmanType::class,
|
|
||||||
'fields' => [
|
|
||||||
[
|
|
||||||
'name' => 'url',
|
|
||||||
'is_required' => true,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
], $response, 'data.meta.types.2');
|
|
||||||
$this->assertInertiaHas([
|
|
||||||
'domain' => '',
|
|
||||||
'name' => '',
|
|
||||||
'type' => [
|
|
||||||
'params' => [],
|
|
||||||
'cls' => null,
|
|
||||||
],
|
|
||||||
], $response, 'data.meta.default');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Mailgateway;
|
|
||||||
|
|
||||||
use App\Mailgateway\Types\MailmanType;
|
|
||||||
use App\Mailman\Support\MailmanService;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Phake;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class MailmanTypeTest extends TestCase
|
|
||||||
{
|
|
||||||
use DatabaseTransactions;
|
|
||||||
|
|
||||||
public function testItChecksForWorks(): void
|
|
||||||
{
|
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
$this->stubIo(MailmanService::class, function ($mock) {
|
|
||||||
Phake::when($mock)->setCredentials('https://example.com', 'user', 'secret')->thenReturn($mock);
|
|
||||||
Phake::when($mock)->check()->thenReturn(true);
|
|
||||||
Phake::when($mock)->setOwner('owner@example.com')->thenReturn($mock);
|
|
||||||
});
|
|
||||||
$type = app(MailmanType::class)->setParams([
|
|
||||||
'url' => 'https://example.com',
|
|
||||||
'user' => 'user',
|
|
||||||
'password' => 'secret',
|
|
||||||
'owner' => 'owner@example.com',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertTrue($type->works());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItCanReturnFalse(): void
|
|
||||||
{
|
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
$this->stubIo(MailmanService::class, function ($mock) {
|
|
||||||
Phake::when($mock)->setCredentials('https://example.com', 'user', 'secret')->thenReturn($mock);
|
|
||||||
Phake::when($mock)->check()->thenReturn(false);
|
|
||||||
});
|
|
||||||
$type = app(MailmanType::class)->setParams([
|
|
||||||
'url' => 'https://example.com',
|
|
||||||
'user' => 'user',
|
|
||||||
'password' => 'secret',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertFalse($type->works());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Mailgateway;
|
|
||||||
|
|
||||||
use App\Mailgateway\Types\LocalType;
|
|
||||||
use App\Mailgateway\Types\MailmanType;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Phake;
|
|
||||||
use Tests\RequestFactories\MailgatewayRequestFactory;
|
|
||||||
use Tests\RequestFactories\MailmanTypeRequest;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class StoreTest extends TestCase
|
|
||||||
{
|
|
||||||
use DatabaseTransactions;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->login()->loginNami();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItCanStoreALocalGateway(): void
|
|
||||||
{
|
|
||||||
$response = $this->postJson('/api/mailgateway', MailgatewayRequestFactory::new()->name('lala')->type(LocalType::class, [])->domain('example.com')->create());
|
|
||||||
|
|
||||||
$response->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseHas('mailgateways', [
|
|
||||||
'domain' => 'example.com',
|
|
||||||
'name' => 'lala',
|
|
||||||
'type' => json_encode([
|
|
||||||
'cls' => LocalType::class,
|
|
||||||
'params' => [],
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItCanStoreAMailmanGateway(): void
|
|
||||||
{
|
|
||||||
$typeParams = ['url' => 'https://example.com', 'user' => 'user', 'password' => 'secret', 'owner' => 'owner@example.com'];
|
|
||||||
$this->stubIo(MailmanType::class, function ($mock) use ($typeParams) {
|
|
||||||
Phake::when($mock)->setParams($typeParams)->thenReturn($mock);
|
|
||||||
Phake::when($mock)->works()->thenReturn(true);
|
|
||||||
Phake::when($mock)->setOwner('owner@example.com')->thenReturn($mock);
|
|
||||||
});
|
|
||||||
$this->postJson('/api/mailgateway', MailgatewayRequestFactory::new()->type(MailmanType::class, MailmanTypeRequest::new()->create($typeParams))->create());
|
|
||||||
|
|
||||||
$this->assertDatabaseHas('mailgateways', [
|
|
||||||
'type' => json_encode([
|
|
||||||
'cls' => MailmanType::class,
|
|
||||||
'params' => $typeParams,
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItThrowsErrorWhenMailmanConnectionFailed(): void
|
|
||||||
{
|
|
||||||
$typeParams = ['url' => 'https://example.com', 'user' => 'user', 'password' => 'secret', 'owner' => 'owner@example.com'];
|
|
||||||
$this->stubIo(MailmanType::class, function ($mock) use ($typeParams) {
|
|
||||||
Phake::when($mock)->setParams($typeParams)->thenReturn($mock);
|
|
||||||
Phake::when($mock)->works()->thenReturn(false);
|
|
||||||
Phake::when($mock)->setOwner('owner@example.com')->thenReturn($mock);
|
|
||||||
});
|
|
||||||
$this->postJson('/api/mailgateway', MailgatewayRequestFactory::new()->type(MailmanType::class, MailmanTypeRequest::new()->create($typeParams))->create())
|
|
||||||
->assertJsonValidationErrors('connection');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItValidatesCustomFields(): void
|
|
||||||
{
|
|
||||||
$typeParams = ['url' => 'https://example.com', 'user' => '', 'password' => 'secret', 'owner' => 'aaaa'];
|
|
||||||
$this->stubIo(MailmanType::class, function ($mock) use ($typeParams) {
|
|
||||||
Phake::when($mock)->setParams($typeParams)->thenReturn($mock);
|
|
||||||
Phake::when($mock)->works()->thenReturn(false);
|
|
||||||
});
|
|
||||||
$this->postJson('/api/mailgateway', MailgatewayRequestFactory::new()->type(MailmanType::class, MailmanTypeRequest::new()->create($typeParams))->create())
|
|
||||||
->assertJsonValidationErrors([
|
|
||||||
'type.params.user' => 'Benutzer ist erforderlich.',
|
|
||||||
'type.params.owner' => 'E-Mail-Adresse des Eigentümers muss eine gültige E-Mail-Adresse sein.',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItValidatesType(): void
|
|
||||||
{
|
|
||||||
$this->postJson('/api/mailgateway', MailgatewayRequestFactory::new()->missingType()->create())
|
|
||||||
->assertJsonValidationErrors('type.cls');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItValidatesNameAndDomain(): void
|
|
||||||
{
|
|
||||||
$this->postJson('/api/mailgateway', MailgatewayRequestFactory::new()->withoutName()->withoutDomain()->create())
|
|
||||||
->assertJsonValidationErrors('domain')
|
|
||||||
->assertJsonValidationErrors('name');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Mailgateway;
|
|
||||||
|
|
||||||
use App\Mailgateway\Models\Mailgateway;
|
|
||||||
use App\Mailgateway\Types\LocalType;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
||||||
use Tests\RequestFactories\MailgatewayRequestFactory;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class UpdateTest extends TestCase
|
|
||||||
{
|
|
||||||
use DatabaseTransactions;
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->login()->loginNami();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItCanUpdateALocalGateway(): void
|
|
||||||
{
|
|
||||||
$mailgateway = Mailgateway::factory()->type(LocalType::class, [])->create();
|
|
||||||
$response = $this->patchJson("/api/mailgateway/{$mailgateway->id}", MailgatewayRequestFactory::new()->name('lala')->type(LocalType::class, [])->domain('example.com')->create());
|
|
||||||
|
|
||||||
$response->assertOk();
|
|
||||||
|
|
||||||
$this->assertDatabaseCount('mailgateways', 1);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\RequestFactories;
|
|
||||||
|
|
||||||
use App\Mailgateway\Types\Type;
|
|
||||||
use Worksome\RequestFactories\RequestFactory;
|
|
||||||
|
|
||||||
class MailgatewayRequestFactory extends RequestFactory
|
|
||||||
{
|
|
||||||
public function definition(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => $this->faker->words(5, true),
|
|
||||||
'type' => [
|
|
||||||
'cls' => app('mail-gateways')->random(),
|
|
||||||
'params' => [],
|
|
||||||
],
|
|
||||||
'domain' => $this->faker->safeEmailDomain(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function name(string $name): self
|
|
||||||
{
|
|
||||||
return $this->state(['name' => $name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function domain(string $domain): self
|
|
||||||
{
|
|
||||||
return $this->state(['domain' => $domain]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param class-string<Type> $type
|
|
||||||
* @param array<string, mixed> $params
|
|
||||||
*/
|
|
||||||
public function type(string $type, array $params): self
|
|
||||||
{
|
|
||||||
return $this->state(['type' => [
|
|
||||||
'cls' => $type,
|
|
||||||
'params' => $params,
|
|
||||||
]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function missingType(): self
|
|
||||||
{
|
|
||||||
return $this->state(['type' => [
|
|
||||||
'cls' => null,
|
|
||||||
'params' => [],
|
|
||||||
]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withoutName(): self
|
|
||||||
{
|
|
||||||
return $this->state(['name' => '']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withoutDomain(): self
|
|
||||||
{
|
|
||||||
return $this->state(['domain' => '']);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace Tests\RequestFactories;
|
namespace Tests\RequestFactories;
|
||||||
|
|
||||||
|
use App\Mailgateway\Types\MailmanType;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Worksome\RequestFactories\RequestFactory;
|
use Worksome\RequestFactories\RequestFactory;
|
||||||
|
|
||||||
class MailmanTypeRequest extends RequestFactory
|
class MailmanTypeRequest extends RequestFactory
|
||||||
|
@ -9,10 +11,48 @@ class MailmanTypeRequest extends RequestFactory
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'url' => 'https://'.$this->faker->domainName(),
|
'url' => 'https://' . $this->faker->domainName(),
|
||||||
'user' => $this->faker->firstName(),
|
'user' => $this->faker->firstName(),
|
||||||
'password' => $this->faker->password(),
|
'password' => $this->faker->password(),
|
||||||
'owner' => $this->faker->safeEmail(),
|
'owner' => $this->faker->safeEmail(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function succeeds($overwrite = []): self
|
||||||
|
{
|
||||||
|
return $this->afterCreating(fn ($model) => $this->fakeCheck([...$model, ...$overwrite], true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fails($overwrite = []): self
|
||||||
|
{
|
||||||
|
return $this->afterCreating(fn ($model) => $this->fakeCheck([...$model, ...$overwrite], false));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fakeCheck($model, bool $check): void
|
||||||
|
{
|
||||||
|
Http::fake(function ($request) use ($model, $check) {
|
||||||
|
if (!$request->hasHeader('Authorization')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$user, $password] = explode(':', base64_decode(str($request->header('Authorization')[0])->replace('Basic ', '')->toString()));
|
||||||
|
|
||||||
|
if ($user !== $model['user'] || $password !== $model['password']) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->url() !== $model['url'] . '/system/versions') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $check
|
||||||
|
? Http::response(['version' => '2.0.0'], 200)
|
||||||
|
: Http::response([], 401);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toData(): MailmanType
|
||||||
|
{
|
||||||
|
return MailmanType::from($this->create());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Testing\AssertableJsonString;
|
use Illuminate\Testing\AssertableJsonString;
|
||||||
use Illuminate\Testing\TestResponse;
|
use Illuminate\Testing\TestResponse;
|
||||||
|
use Livewire\Features\SupportTesting\Testable;
|
||||||
use Phake;
|
use Phake;
|
||||||
use PHPUnit\Framework\Assert;
|
use PHPUnit\Framework\Assert;
|
||||||
use Tests\Lib\MakesHttpCalls;
|
use Tests\Lib\MakesHttpCalls;
|
||||||
|
@ -172,5 +173,14 @@ class TestCase extends BaseTestCase
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Testable::macro('setArray', function ($attributes) {
|
||||||
|
$self = $this;
|
||||||
|
foreach ($attributes as $key => $value) {
|
||||||
|
$self = $this->set($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue