diff --git a/app/Maildispatcher/Actions/IndexAction.php b/app/Maildispatcher/Actions/IndexAction.php new file mode 100644 index 00000000..151f6b23 --- /dev/null +++ b/app/Maildispatcher/Actions/IndexAction.php @@ -0,0 +1,15 @@ +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])); + } +} diff --git a/app/Maildispatcher/Actions/StoreAction.php b/app/Maildispatcher/Actions/StoreAction.php new file mode 100644 index 00000000..59605444 --- /dev/null +++ b/app/Maildispatcher/Actions/StoreAction.php @@ -0,0 +1,41 @@ + $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); + } +} diff --git a/app/Maildispatcher/Data/MailEntry.php b/app/Maildispatcher/Data/MailEntry.php new file mode 100644 index 00000000..b93d9777 --- /dev/null +++ b/app/Maildispatcher/Data/MailEntry.php @@ -0,0 +1,12 @@ +belongsTo(Maildispatcher::class); + } +} diff --git a/app/Maildispatcher/Models/Maildispatcher.php b/app/Maildispatcher/Models/Maildispatcher.php new file mode 100644 index 00000000..5cc1a535 --- /dev/null +++ b/app/Maildispatcher/Models/Maildispatcher.php @@ -0,0 +1,27 @@ + 'json', + ]; + + public function gateway(): BelongsTo + { + return $this->belongsTo(Mailgateway::class); + } +} diff --git a/app/Mailgateway/Types/LocalType.php b/app/Mailgateway/Types/LocalType.php index 6e879254..a214a0f6 100644 --- a/app/Mailgateway/Types/LocalType.php +++ b/app/Mailgateway/Types/LocalType.php @@ -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(); + } } diff --git a/app/Mailgateway/Types/MailmanType.php b/app/Mailgateway/Types/MailmanType.php index cd5b0ae2..a624c085 100644 --- a/app/Mailgateway/Types/MailmanType.php +++ b/app/Mailgateway/Types/MailmanType.php @@ -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 + { + } } diff --git a/app/Mailgateway/Types/Type.php b/app/Mailgateway/Types/Type.php index a647167b..b9dc60d8 100644 --- a/app/Mailgateway/Types/Type.php +++ b/app/Mailgateway/Types/Type.php @@ -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 + */ + abstract public function list(string $name, string $domain): Collection; + /** * @param array $params */ @@ -48,6 +62,24 @@ abstract class Type ]; } + /** + * @param Collection $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 */ diff --git a/app/Member/MemberController.php b/app/Member/MemberController.php index dd8a55ee..f152a232 100644 --- a/app/Member/MemberController.php +++ b/app/Member/MemberController.php @@ -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(); } diff --git a/app/Member/MemberRequest.php b/app/Member/MemberRequest.php index cd273587..12cb8397 100644 --- a/app/Member/MemberRequest.php +++ b/app/Member/MemberRequest.php @@ -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(); } } diff --git a/database/factories/Maildispatcher/Models/MaildispatcherFactory.php b/database/factories/Maildispatcher/Models/MaildispatcherFactory.php new file mode 100644 index 00000000..47ebcbd5 --- /dev/null +++ b/database/factories/Maildispatcher/Models/MaildispatcherFactory.php @@ -0,0 +1,23 @@ + + */ +class MaildispatcherFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition() + { + return [ + // + ]; + } +} diff --git a/database/migrations/2023_06_12_083133_create_maildispatchers_table.php b/database/migrations/2023_06_12_083133_create_maildispatchers_table.php new file mode 100644 index 00000000..8507cb48 --- /dev/null +++ b/database/migrations/2023_06_12_083133_create_maildispatchers_table.php @@ -0,0 +1,32 @@ +uuid('id'); + $table->string('name'); + $table->uuid('gateway_id'); + $table->json('filter'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('maildispatchers'); + } +}; diff --git a/database/migrations/2023_06_12_093205_create_localmaildispatchers_table.php b/database/migrations/2023_06_12_093205_create_localmaildispatchers_table.php new file mode 100644 index 00000000..22185739 --- /dev/null +++ b/database/migrations/2023_06_12_093205_create_localmaildispatchers_table.php @@ -0,0 +1,32 @@ +uuid('id'); + $table->string('from'); + $table->string('to'); + $table->unique(['from', 'to']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('localmaildispatchers'); + } +}; diff --git a/routes/web.php b/routes/web.php index ef12a4d1..350b7be0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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'); }); diff --git a/tests/Feature/Maildispatcher/StoreTest.php b/tests/Feature/Maildispatcher/StoreTest.php new file mode 100644 index 00000000..ca0d290e --- /dev/null +++ b/tests/Feature/Maildispatcher/StoreTest.php @@ -0,0 +1,51 @@ +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', + ]); + } +}