--wip-- [skip ci]

This commit is contained in:
Philipp Lang 2023-06-12 13:12:46 +02:00
parent da3197395f
commit a74d0936a2
16 changed files with 383 additions and 1 deletions

View File

@ -0,0 +1,15 @@
<?php
namespace App\Maildispatcher\Actions;
use Lorisleiva\Actions\Concerns\AsAction;
class IndexAction
{
use AsAction;
public function handle()
{
// ...
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Maildispatcher\Actions;
use App\Maildispatcher\Data\MailEntry;
use App\Maildispatcher\Models\Maildispatcher;
use App\Member\FilterScope;
use App\Member\Member;
use Illuminate\Support\Collection;
use Lorisleiva\Actions\Concerns\AsAction;
class ResyncAction
{
use AsAction;
public function handle()
{
foreach (Maildispatcher::get() as $dispatcher) {
$dispatcher->gateway->type->sync($dispatcher->name, $dispatcher->gateway->domain, $this->getResults($dispatcher));
}
}
public function getResults(Maildispatcher $dispatcher): Collection
{
return Member::search(data_get($dispatcher->filter, 'search', ''))->query(
fn ($q) => $q->select('*')->withFilter(FilterScope::fromPost($dispatcher->filter))
)->get()->filter(fn ($member) => $member->email || $member->email_parents)->map(fn ($member) => MailEntry::from(['email' => $member->email ?: $member->email_parents]));
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Maildispatcher\Actions;
use App\Maildispatcher\Models\Maildispatcher;
use Illuminate\Http\JsonResponse;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
class StoreAction
{
use AsAction;
/**
* @param array<string, mixed> $input
*/
public function handle(array $input): void
{
Maildispatcher::create([
...$input,
'filter' => (object) $input['filter'],
]);
ResyncAction::dispatch();
}
public function rules(): array
{
return [
'gateway_id' => 'required|exists:mailgateways,id',
'name' => 'required|max:50',
'filter' => 'present|array',
];
}
public function asController(ActionRequest $request): JsonResponse
{
$this->handle($request->validated());
return response()->json('', 201);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Maildispatcher\Data;
use Spatie\LaravelData\Data;
class MailEntry extends Data
{
public function __construct(public string $email)
{
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Maildispatcher\Models;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Localmaildispatcher extends Model
{
use HasFactory;
use HasUuids;
public $guarded = [];
public $timestamps = false;
public function dispatcher(): BelongsTo
{
return $this->belongsTo(Maildispatcher::class);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Maildispatcher\Models;
use App\Mailgateway\Models\Mailgateway;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Maildispatcher extends Model
{
use HasFactory;
use HasUuids;
public $guarded = [];
public $timestamps = false;
public $casts = [
'filter' => 'json',
];
public function gateway(): BelongsTo
{
return $this->belongsTo(Mailgateway::class);
}
}

View File

@ -2,6 +2,10 @@
namespace App\Mailgateway\Types;
use App\Maildispatcher\Data\MailEntry;
use App\Maildispatcher\Models\Localmaildispatcher;
use Illuminate\Support\Collection;
class LocalType extends Type
{
public static function name(): string
@ -23,4 +27,34 @@ class LocalType extends Type
{
return $this;
}
/**
* {@inheritdoc}
*/
public function list(string $name, string $domain): Collection
{
return Localmaildispatcher::where('from', "{$name}@{$domain}")->get()->map(fn ($mail) => MailEntry::from(['email' => $mail->to]));
}
public function search(string $name, string $domain, string $email): ?MailEntry
{
$result = Localmaildispatcher::where('from', "{$name}@{$domain}")->where('to', $email)->first();
return $result ? MailEntry::from([
'email' => $result->to,
]) : null;
}
public function add(string $name, string $domain, string $email): void
{
Localmaildispatcher::create([
'from' => "{$name}@{$domain}",
'to' => $email,
]);
}
public function remove(string $name, string $domain, string $email): void
{
Localmaildispatcher::where('from', "{$name}@{$domain}")->where('to', $email)->delete();
}
}

View File

@ -2,7 +2,9 @@
namespace App\Mailgateway\Types;
use App\Maildispatcher\Data\MailEntry;
use App\Mailman\Support\MailmanService;
use Illuminate\Support\Collection;
class MailmanType extends Type
{
@ -61,4 +63,26 @@ class MailmanType extends Type
],
];
}
public function search(string $name, string $domain, string $email): ?MailEntry
{
return null;
}
public function add(string $name, string $domain, string $email): void
{
return;
}
/**
* {@inheritdoc}
*/
public function list(string $name, string $domain): Collection
{
return collect([]);
}
public function remove(string $name, string $domain, string $email): void
{
}
}

View File

@ -2,6 +2,9 @@
namespace App\Mailgateway\Types;
use App\Maildispatcher\Data\MailEntry;
use Illuminate\Support\Collection;
abstract class Type
{
abstract public static function name(): string;
@ -13,6 +16,17 @@ abstract class Type
abstract public function works(): bool;
abstract public function search(string $name, string $domain, string $email): ?MailEntry;
abstract public function add(string $name, string $domain, string $email): void;
abstract public function remove(string $name, string $domain, string $email): void;
/**
* @return Collection<int, MailEntry>
*/
abstract public function list(string $name, string $domain): Collection;
/**
* @param array<string, mixed> $params
*/
@ -48,6 +62,24 @@ abstract class Type
];
}
/**
* @param Collection<int, MailEntry> $results
*/
public function sync(string $name, string $domain, Collection $results): void
{
foreach ($results as $result) {
if ($this->search($name, $domain, $result->email)) {
continue;
}
$this->add($name, $domain, $result->email);
}
$this->list($name, $domain)
->filter(fn ($listEntry) => null === $results->first(fn ($r) => $r->email === $listEntry->email))
->each(fn ($listEntry) => $this->remove($name, $domain, $listEntry->email));
}
/**
* @return array<string, string>
*/

View File

@ -4,6 +4,7 @@ namespace App\Member;
use App\Country;
use App\Http\Controllers\Controller;
use App\Maildispatcher\Actions\ResyncAction;
use App\Setting\GeneralSettings;
use App\Setting\NamiSettings;
use Illuminate\Http\RedirectResponse;
@ -86,6 +87,7 @@ class MemberController extends Controller
}
$member->delete();
ResyncAction::dispatch();
return redirect()->back();
}

View File

@ -5,6 +5,7 @@ namespace App\Member;
use App\Activity;
use App\Group;
use App\Invoice\BillKind;
use App\Maildispatcher\Actions\ResyncAction;
use App\Member\Actions\NamiPutMemberAction;
use App\Setting\NamiSettings;
use App\Subactivity;
@ -99,6 +100,7 @@ class MemberRequest extends FormRequest
Subactivity::find($this->input('first_subactivity_id')),
);
}
ResyncAction::dispatch();
}
public function persistUpdate(Member $member): void
@ -118,5 +120,6 @@ class MemberRequest extends FormRequest
if (!$this->input('has_nami') && null !== $member->nami_id) {
DeleteJob::dispatch($member->nami_id);
}
ResyncAction::dispatch();
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Database\Factories\Maildispatcher\Models;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Maildispatcher\Models\Maildispatcher>
*/
class MaildispatcherFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
//
];
}
}

View File

@ -0,0 +1,32 @@
<?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('maildispatchers', function (Blueprint $table) {
$table->uuid('id');
$table->string('name');
$table->uuid('gateway_id');
$table->json('filter');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('maildispatchers');
}
};

View File

@ -0,0 +1,32 @@
<?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('localmaildispatchers', function (Blueprint $table) {
$table->uuid('id');
$table->string('from');
$table->string('to');
$table->unique(['from', 'to']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('localmaildispatchers');
}
};

View File

@ -20,6 +20,7 @@ use App\Initialize\Actions\InitializeFormAction;
use App\Initialize\Actions\NamiGetSearchLayerAction;
use App\Initialize\Actions\NamiLoginCheckAction;
use App\Initialize\Actions\NamiSearchAction;
use App\Maildispatcher\Actions\StoreAction as MaildispatcherStoreAction;
use App\Mailgateway\Actions\StoreAction;
use App\Mailgateway\Actions\UpdateAction;
use App\Member\Actions\ExportAction;
@ -81,7 +82,8 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::get('/contribution-generate', ContributionGenerateAction::class)->name('contribution.generate');
Route::post('/contribution-validate', ContributionValidateAction::class)->name('contribution.validate');
// -------------------------------- Mailgateway --------------------------------
// ----------------------------------- mail ------------------------------------
Route::post('/api/mailgateway', StoreAction::class)->name('mailgateway.store');
Route::patch('/api/mailgateway/{mailgateway}', UpdateAction::class)->name('mailgateway.update');
Route::post('/api/maildispatcher', MaildispatcherStoreAction::class)->name('maildispatcher.index');
});

View File

@ -0,0 +1,51 @@
<?php
namespace Tests\Feature\Maildispatcher;
use App\Activity;
use App\Maildispatcher\Models\Maildispatcher;
use App\Mailgateway\Models\Mailgateway;
use App\Mailgateway\Types\LocalType;
use App\Member\Member;
use App\Member\Membership;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class StoreTest extends TestCase
{
use DatabaseTransactions;
public function setUp(): void
{
parent::setUp();
$this->login()->loginNami();
}
public function testItCanStoreAMail(): void
{
$gateway = Mailgateway::factory()->type(LocalType::class, [])->domain('example.com')->create();
Member::factory()->defaults()->create();
Member::factory()->defaults()->has(Membership::factory()->inLocal('Leiter*in', 'Wölfling'))->create(['email' => 'jane@example.com']);
$activityId = Activity::first()->id;
$response = $this->postJson('/api/maildispatcher', [
'name' => 'test',
'gateway_id' => $gateway->id,
'filter' => ['activity_id' => $activityId],
]);
$response->assertStatus(201);
$this->assertDatabaseHas('maildispatchers', [
'name' => 'test',
'gateway_id' => $gateway->id,
'filter' => "{\"activity_id\":{$activityId}}",
]);
$dispatcher = Maildispatcher::first();
$this->assertDatabaseCount('localmaildispatchers', 1);
$this->assertDatabaseHas('localmaildispatchers', [
'from' => 'test@example.com',
'to' => 'jane@example.com',
]);
}
}